Roslynとは何か ── C#コードをコンパイラの視点で読む・直す・生成する

· · .NET, CSharp, Roslyn, Analyzer, SourceGenerator, Compiler, StaticAnalysis, CodeGeneration, 既存資産活用

1. 最初に押さえるべきこと

C# のソースコードを処理したい場面は、意外と多くあります。たとえばこういう作業です。

特定のAPIの使い方を禁止したい
古い書き方を機械的に見つけたい
メソッドやクラスの一覧を収集したい
プロジェクト全体から依存関係を調べたい
定型コードをコンパイル時に生成したい
自社ライブラリの誤用をビルド時に警告したい
大規模な置換や移行を安全に進めたい

このようなとき、単純に *.cs ファイルを開いて文字列検索や正規表現で処理したくなることがあります。

しかし、C# は文字列ではありません。この 2 つは、見た目は似ていても、意味としては別物です。

Console.WriteLine("Hello");
MyCompany.Logging.Console.WriteLine("Hello");

また、Console という名前が別の型を指すこともあります。

using Console = MyCompany.Logging.Console;

Console.WriteLine("Hello");

文字列として見れば、どれも Console.WriteLine に見えます。 しかし、コンパイラから見れば、それが System.Console.WriteLine なのか、別の型なのかは、名前解決をしなければ分かりません。

ここで登場するのが Roslyn です。Roslyn は、C# や Visual Basic のコンパイラが持っている情報を、アプリケーションやツールから API として利用できるようにした基盤です。

簡単に言うと、Roslyn を使うことで、C# のコードをこう扱えるようになります。

文字列としてではなく、構文として読む
見た目としてではなく、意味として読む
単一ファイルではなく、プロジェクトやソリューションとして読む
読んだ結果をもとに、警告・修正案・生成コードを出す

この記事では、Roslyn の全体像、Syntax Tree、SemanticModel、Workspace、Analyzer、Source Generator、そして実務での使いどころを整理します。

なお、この記事に登場するコードは、ビルド・実行できるサンプル一式(Syntax Tree / SemanticModel を扱うライブラリ、DateTime.Now を警告する Analyzer、Source Generator、ソリューション全体を解析するデモ、誤検出・検出漏れを確認するユニットテスト)として GitHub で公開しています。

roslyn-dotnet-compiler-platform - komurasoft-blog-samples (GitHub)

2. Roslynとは何か

Roslyn は、正式には .NET Compiler Platform と呼ばれます。C# と Visual Basic のコンパイラ実装であり、同時にコード解析ツールを作るための API 群でもあります。

従来、コンパイラはこういう「黒い箱」として扱われがちでした。

ソースコードを入れる
コンパイラが処理する
DLL / EXE が出てくる

開発者がコンパイラの内部で作られる情報にアクセスすることは、普通はできませんでした。

しかし、実際のコンパイラは、単にテキストを機械語や IL に変換しているわけではなく、コンパイル中にこうした情報を作っています。

この文字列はクラス宣言である
この識別子はローカル変数である
このメソッド呼び出しはこの型のこのメソッドを指している
この式の戻り値の型は string である
このコードは構文エラーである
この参照はアセンブリAの型を指している
このusingは実際には使われていない

Roslyn は、このような情報を開発者が利用できるようにします。そのため、Roslyn は単なるコンパイラではなく、コードを理解するためのプラットフォームです。

3. Roslynを使うと何ができるのか

Roslyn を使うと、主にこういうことができます。

C# / VB の構文解析
型やメソッドの意味解析
コンパイル情報の取得
プロジェクトやソリューション全体の解析
独自Analyzerの作成
Code Fixの作成
Source Generatorの作成
リファクタリングツールの作成
コード生成
コード変換

もう少し実務寄りに書くと、こういう使い方です。

禁止APIを使ったらビルド警告にする
古いAPIを使っている箇所を一覧化する
asyncメソッドの命名ルールをチェックする
IDisposableの扱い漏れを検出する
自社フレームワークの正しい使い方を誘導する
DTOやマッピングコードをコンパイル時に生成する
設定ファイルや属性から定型コードを生成する
.NET Frameworkから.NETへの移行調査を補助する

Roslyn が重要なのは、「C# のソースコードを扱う処理」を、コンパイラと同じ土台の上で書けることです。

正規表現や独自パーサーで C# を読むと、すぐに限界が来ます。たとえば、こうした要素を正しく扱うのは簡単ではありません。

using alias
拡張メソッド
partial class
partial method
global using
nullable annotations
ジェネリック型
オーバーロード解決
条件付きコンパイル
プリプロセッサディレクティブ
コメントや空白を維持した書き換え

Roslyn は、これらを C# の言語仕様に沿って扱うための API を提供します。

4. Roslynは「構文」と「意味」を分けて考える

Roslyn を理解するときに、最初に分けておきたいのがこの 2 つです。

構文: コードがどう書かれているか
意味: そのコードが何を指しているか

たとえば、このコードを見ます。

Console.WriteLine(message);

構文として見ると、こういう形です。

式文
  呼び出し式
    メンバーアクセス式
      識別子 Console
      識別子 WriteLine
    引数 message

しかし、これだけでは意味は分かりません。Console がどの型なのか、WriteLine がどのオーバーロードなのか、message の型が何なのかは、構文だけでは判断できません。

意味として見るには、これだけの情報が必要です。

using の状態
参照アセンブリ
同じプロジェクト内の型定義
別プロジェクトへの参照
型推論
オーバーロード解決
言語バージョン
nullable context

Roslyn では、この違いが API としても分かれています。

Syntax Tree     : コードの構文を表す
SemanticModel   : 構文が何を意味するかを表す
Compilation     : コンパイルに必要な全体情報を表す
Workspace       : ソリューション、プロジェクト、ドキュメントを扱う

この区別を押さえると、Roslyn の見通しがかなり良くなります。

5. Syntax Treeとは何か

Syntax Tree は、ソースコードの構文構造を表す木です。たとえば、こんなコードがあるとします。

class User
{
    public string Name { get; set; }

    public void Rename(string name)
    {
        Name = name;
    }
}

このコードは、Roslyn から見ると、ざっくりこういう構造です。

CompilationUnit
  ClassDeclaration: User
    PropertyDeclaration: Name
    MethodDeclaration: Rename
      Parameter: name
      Block
        ExpressionStatement
          AssignmentExpression

Syntax Tree は、テキストを単に行ごとに分割したものではなく、クラス、メソッド、プロパティ、式、ステートメント、引数、演算子など、C# の構文要素として整理された構造です。

簡単な例を見ます。

using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

var source = """
class User
{
    public string Name { get; set; }

    public void Rename(string name)
    {
        Name = name;
    }
}
""";

var tree = CSharpSyntaxTree.ParseText(source);
var root = tree.GetCompilationUnitRoot();

var methods = root
    .DescendantNodes()
    .OfType<MethodDeclarationSyntax>();

foreach (var method in methods)
{
    Console.WriteLine(method.Identifier.Text);
}

このコードは、ソースコードの中からメソッド宣言を探し、メソッド名を出力します。この例では Rename が取得できます。

文字列検索で void を探すのではなく、C# の構文として「メソッド宣言」を探している点が重要です。

6. Node、Token、Trivia

Syntax Tree を扱うときは、この 3 つの言葉がよく出てきます。

SyntaxNode
SyntaxToken
SyntaxTrivia

SyntaxNode

SyntaxNode は、構文上のまとまりです。たとえばこういうものです。

クラス宣言
メソッド宣言
プロパティ宣言
if文
for文
代入式
呼び出し式
ラムダ式

C# の構文上、さらに子要素を持つようなものが Node です。

SyntaxToken

SyntaxToken は、構文を構成する最小単位です。たとえばこのあたりです。

class キーワード
public キーワード
識別子 User
識別子 Rename
{ や }
; や ,
文字列リテラル
数値リテラル

Token は、構文木の末端にある要素です。

SyntaxTrivia

SyntaxTrivia は、通常の意味解析には直接関係しないけれど、ソースコードを再現するためには必要な情報です。たとえばこうしたものです。

空白
改行
コメント
プリプロセッサディレクティブ

この Trivia があるため、Roslyn はコメントや空白を含めてソースコードを高い忠実度で扱えます。

コード整形、リファクタリング、機械的な書き換えを行うときに、Trivia はとても重要です。

単に AST を作るだけならコメントは捨ててもよさそうに見えます。 しかし、実務のコード変換では、コメントや改行を壊さないことが重要です。

7. Syntax Treeは不変である

Roslyn の Syntax Tree は不変です。つまり、取得した構文木を直接書き換えるのではなく、変更を加えた新しい構文木を作ります。

たとえば、メソッド名を変えたい場合も、既存の MethodDeclarationSyntax をその場で変更するわけではありません。

var newMethod = oldMethod.WithIdentifier(
    SyntaxFactory.Identifier("NewName"));

このように、新しいノードを作ります。

不変であることには、いくつかの利点があります。

複数スレッドで扱いやすい
IDEで編集中のスナップショットを安全に扱える
差分を作りやすい
変更前と変更後を比較しやすい

最初は少し面倒に感じるかもしれません。しかし、IDE、ビルド、Analyzer、Source Generator のように、同じコードを複数の処理が同時に参照する世界では、不変性は大きな利点になります。

8. SemanticModelとは何か

Syntax Tree だけでは、コードの見た目しか分かりません。意味を知るには SemanticModel を使います。

たとえば、このコードを考えます。

Console.WriteLine("Hello");

Syntax Tree からは、Console という識別子と WriteLine という識別子があることは分かります。しかし、それが System.Console.WriteLine(string?) を指すのか、別の型のメソッドを指すのかは分かりません。

SemanticModel を使うと、「この構文ノードがどのシンボルに解決されたか」を取得できます。

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

var source = """
using System;

class Program
{
    static void Main()
    {
        Console.WriteLine("Hello");
    }
}
""";

var tree = CSharpSyntaxTree.ParseText(source);

var compilation = CSharpCompilation.Create(
    assemblyName: "Sample",
    syntaxTrees: new[] { tree },
    references: new[]
    {
        MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
        MetadataReference.CreateFromFile(typeof(Console).Assembly.Location)
    });

var semanticModel = compilation.GetSemanticModel(tree);
var root = tree.GetCompilationUnitRoot();

var invocation = root
    .DescendantNodes()
    .OfType<InvocationExpressionSyntax>()
    .First();

var symbolInfo = semanticModel.GetSymbolInfo(invocation);
var method = (IMethodSymbol?)symbolInfo.Symbol;

Console.WriteLine(method?.ContainingType.ToDisplayString());
Console.WriteLine(method?.Name);

このようにすると、Console.WriteLine が実際にどのメソッドとして解決されたかを取得できます。

構文だけではなく、コンパイラの名前解決結果を使えることが Roslyn の強みです。

9. Symbolとは何か

Roslyn では、型、メソッド、プロパティ、フィールド、引数、ローカル変数などを Symbol として扱います。

代表的なインターフェイスを挙げます。

INamedTypeSymbol  : クラス、構造体、インターフェイスなど
IMethodSymbol     : メソッド
IPropertySymbol   : プロパティ
IFieldSymbol      : フィールド
IParameterSymbol  : 引数
ILocalSymbol      : ローカル変数
INamespaceSymbol  : 名前空間

Symbol は、ソースコード上の見た目ではなく、コンパイラが解決した意味を表します。

たとえば、この 2 つのコードは、見た目は違います。

System.Console.WriteLine("Hello");
using System;

Console.WriteLine("Hello");

しかし、どちらも同じ System.Console.WriteLine を指しているなら、Roslyn の意味解析では同じメソッドシンボルとして扱えます。

この性質により、こうした判定ができます。

この呼び出しは本当に自社で禁止しているAPIか
この型は特定のインターフェイスを実装しているか
このメソッドは async か
この戻り値は nullable か
この属性は実際に付いているか
このクラスは特定の基底クラスを継承しているか

単なる文字列検索ではなく、コンパイラの判断に基づく解析ができます。

10. Compilationとは何か

Compilation は、C# や Visual Basic のプログラムをコンパイルするために必要な情報をまとめたものです。

具体的には、こうした情報を持ちます。

SyntaxTree の集合
参照アセンブリ
コンパイルオプション
言語バージョン
定義済みシンボル
型やメンバーの情報
診断情報

単一ファイルを構文として読むだけなら SyntaxTree で十分です。しかし、型解決や参照解決を行うには Compilation が必要です。

たとえば、こういうことをしたい場合です。

このメソッド呼び出しがどの型のメソッドか知りたい
このクラスがIDisposableを実装しているか知りたい
この属性が実際にどの属性型か知りたい
この式の戻り値型を知りたい
コンパイルエラーや警告を取得したい

これらは、構文だけでは判断できません。プロジェクトの参照やコンパイルオプションまで含めて考える必要があります。

11. Workspaceとは何か

単一ファイルではなく、ソリューションやプロジェクト全体を扱いたい場合は Workspace を使います。

Workspace は、こういう単位を扱います。

Solution
Project
Document

たとえば、ソリューション全体から全プロジェクトを読み込み、全ドキュメントを解析するような場合です。

using Microsoft.Build.Locator;
using Microsoft.CodeAnalysis.MSBuild;

MSBuildLocator.RegisterDefaults();

using var workspace = MSBuildWorkspace.Create();
var solution = await workspace.OpenSolutionAsync("Sample.sln");

foreach (var project in solution.Projects)
{
    Console.WriteLine(project.Name);

    foreach (var document in project.Documents)
    {
        var root = await document.GetSyntaxRootAsync();
        Console.WriteLine($"  {document.Name}: {root?.DescendantNodes().Count()} nodes");
    }
}

このようなツールを作ると、既存コードベースの調査や移行支援に使えます。

たとえば、こういう用途です。

特定のAPI呼び出し一覧をCSV化する
古い名前空間の利用箇所を一覧化する
public APIの一覧を作る
プロジェクト間依存関係を調べる
巨大なソリューションのコード規約違反を調べる
機械的なコード変換を行う

Analyzer は IDE やビルドと一体で動く仕組みです。 一方、Workspace を使ったコンソールツールは、調査や一括移行に向いています。

この 2 つは似ていますが、使いどころを分けるとよいです。

12. Roslynを使う代表的な形

Roslyn の使い方は、大きく分けると 4 つです。

1. ライブラリとして使う
2. Analyzerを作る
3. Code Fixを作る
4. Source Generatorを作る

それぞれ目的が違います。

ライブラリとして使う

自作のコンソールアプリや社内ツールから Roslyn API を呼び出します。

向いている用途はこのあたりです。

コードベースの調査
一括変換
メトリクス収集
移行支援
レポート作成

この形では、好きなタイミングでツールを実行できます。IDE の入力中に動く必要がないため、ある程度重い処理も許容しやすいです。

Analyzerを作る

Analyzer は、コードを解析して警告やエラーを出す仕組みです。

たとえば、こういうルールを作れます。

DateTime.Now を使わず DateTimeOffset.UtcNow を使う
asyncメソッド名はAsyncで終わる
ライブラリの初期化APIを誤った順序で呼ばない
特定のnamespaceを新規コードで使わない
Task.Result / Wait の利用を禁止する

Analyzer は、Visual Studio やビルド時に動かせます。チームの規約やライブラリの使い方を、レビュー担当者の記憶に頼らず、機械的に検出できます。

Code Fixを作る

Code Fix は、Analyzer が見つけた問題に対して、修正案を提示する仕組みです。

Visual Studio の電球アイコンから適用できる修正を想像すると分かりやすいです。

たとえば、Analyzer がこのコードを検出したとします。

DateTime.Now

Code Fix で、こういう修正を提示できます。

DateTimeOffset.UtcNow

単に「警告を出す」だけでなく、「安全な直し方」を自動化できるのが Code Fix の強みです。

Source Generatorを作る

Source Generator は、コンパイル時にコードを生成し、そのコードを同じコンパイルに追加する仕組みです。

たとえば、こんな用途があります。

属性が付いたクラスから定型コードを生成する
設定ファイルから型安全なアクセサを生成する
DTOのマッピングコードを生成する
シリアライザー用コードを生成する
enumと文字列の変換コードを生成する
ルーティングやDI登録コードを生成する

実行時に Reflection で情報を集めていた処理を、コンパイル時の生成コードに置き換えられる場合があります。これにより、起動時コストの削減や、AOT との相性改善につながることがあります。

13. Analyzerは「自動コードレビュー」として使える

Analyzer は、実務では「自動コードレビュー」と考えると分かりやすいです。

コードレビューでは、毎回同じ指摘が出ることがあります。

このAPIは使わないでください
このメソッド名は規約に合っていません
このcatchは握りつぶしです
このnullチェックは不要です
この呼び出しはパフォーマンス上問題があります

人間が毎回指摘しているなら、そのうちの一部は Analyzer にできます。

特に Analyzer に向いているのは、こういうルールです。

明確に良し悪しを判定できる
例外が少ない
修正方針が決まっている
チーム全体で守りたい
レビューで頻出している
ビルドで止めてもよい

逆に、Analyzer に向いていないものもあります。

文脈依存で判断が難しい
設計判断が必要
例外が多すぎる
人によって意見が分かれる
警告が多すぎて誰も見なくなる

Analyzer は強い道具です。強い道具なので、入れすぎると開発体験を悪くします。最初は、少数の重要なルールから始めるのがよいです。

14. .NET SDKに含まれるAnalyzerから始める

独自 Analyzer を書く前に、まずは .NET SDK に含まれる Analyzer を確認するのが現実的です。

.NET 5 以降のプロジェクトでは、.NET のコード分析が既定で有効になっています。

よく見る診断 ID には、この 2 系統があります。

CAxxxx : コード品質、信頼性、性能、セキュリティなど
IDExxxx: コードスタイル、IDE支援など

Analyzer の重大度は .editorconfig で調整できます。

# 例: 未使用のusingを警告にする
dotnet_diagnostic.IDE0005.severity = warning

# 例: CA2000をエラーにする
dotnet_diagnostic.CA2000.severity = error

プロジェクトファイルで有効化や厳格化を行うこともあります。

<PropertyGroup>
  <EnableNETAnalyzers>true</EnableNETAnalyzers>
  <AnalysisLevel>latest</AnalysisLevel>
  <TreatWarningsAsErrors>false</TreatWarningsAsErrors>
</PropertyGroup>

既存プロジェクトに導入すると、最初は警告が大量に出ることがあります。その場合は、いきなり全部をエラーにするのではなく、段階的に進めるとよいです。

まず全体の警告数を把握する
新規コードで増やさない方針にする
重要なルールだけwarningにする
本当に守りたいルールだけerrorにする
既存違反は計画的に減らす

独自 Analyzer は、この土台の上で「自社固有のルール」を補うものと考えるとよいです。

15. Analyzerの最小イメージ

Analyzer は、特定の構文やシンボルを見つけて Diagnostic を報告します。

たとえば、DateTime.Now の使用を警告する Analyzer を考えます。

実際の製品コードでは、型解決や例外ケースを丁寧に扱う必要がありますが、最小イメージは次のようになります。

using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;

[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class NoDateTimeNowAnalyzer : DiagnosticAnalyzer
{
    private static readonly DiagnosticDescriptor Rule = new(
        id: "CMP001",
        title: "DateTime.Nowを直接使わない",
        messageFormat: "DateTime.Nowではなく、用途に応じてDateTimeOffset.UtcNowなどを検討してください",
        category: "Usage",
        defaultSeverity: DiagnosticSeverity.Warning,
        isEnabledByDefault: true);

    public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
        => ImmutableArray.Create(Rule);

    public override void Initialize(AnalysisContext context)
    {
        context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
        context.EnableConcurrentExecution();

        context.RegisterSyntaxNodeAction(
            AnalyzeMemberAccess,
            SyntaxKind.SimpleMemberAccessExpression);
    }

    private static void AnalyzeMemberAccess(SyntaxNodeAnalysisContext context)
    {
        var memberAccess = (MemberAccessExpressionSyntax)context.Node;

        if (memberAccess.Name.Identifier.Text != "Now")
        {
            return;
        }

        var symbol = context.SemanticModel.GetSymbolInfo(memberAccess).Symbol;
        if (symbol is not IPropertySymbol propertySymbol)
        {
            return;
        }

        if (propertySymbol.Name == "Now" &&
            propertySymbol.ContainingType.ToDisplayString() == "System.DateTime")
        {
            var diagnostic = Diagnostic.Create(Rule, memberAccess.GetLocation());
            context.ReportDiagnostic(diagnostic);
        }
    }
}

この例で大事なのは、単に文字列として DateTime.Now を探していないことです。

SemanticModel を使って、実際に System.DateTime.Now を指しているかを確認しています。

そのため、こうした別物を誤検出しにくくなります。

MyCompany.DateTime.Now

Analyzer では、このように構文で候補を絞り、意味解析で本当に対象か確認する、という流れがよく使われます。

Syntaxで高速に候補を探す
SemanticModelで正確に判定する
Diagnosticで場所とメッセージを報告する

16. Code Fixは「直し方」まで配る仕組み

Analyzer は問題を見つけ、Code Fix はその問題の直し方を提示します。

たとえば、DateTime.Now を検出したときに、こんな修正案を提示できます。

DateTimeOffset.UtcNow に置き換える
IClock.Now のような抽象化に置き換える

ただし、Code Fix は慎重に設計する必要があります。DateTime.Now を常に DateTimeOffset.UtcNow に変えればよいとは限りません。ローカル時刻を表示したい場合と、保存・比較用の時刻を扱いたい場合では、適切な型やタイムゾーンの扱いが違います。

そのため、Code Fix はこういう条件を満たすときに向いています。

修正後の意味が明確である
副作用が小さい
機械的な変換で安全に直せる
人間が確認しやすい

たとえば、この手の修正は Code Fix と相性がよいです。

古いAPI名を新しいAPI名に置き換える
不足しているusingを追加する
命名規則に沿って名前を変える
属性を追加する
不要な引数を削除する

一方で、設計判断が必要な修正は、自動修正より警告だけにした方がよい場合があります。

17. Source Generatorは「コンパイル時のコード生成」

Source Generator は、コンパイル時に実行され、生成した C# コードを同じコンパイルに追加します。

イメージとしてはこういう流れです。

ユーザーのソースコードを読む
属性や型定義を調べる
必要なC#コードを生成する
生成したコードをコンパイル対象に追加する

簡単な Source Generator の例です。

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
using System.Text;

[Generator]
public sealed class BuildInfoGenerator : IIncrementalGenerator
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        context.RegisterPostInitializationOutput(static ctx =>
        {
            var source = """
namespace Generated;

public static class BuildInfo
{
    public static string Tool => "Roslyn Source Generator";
}
""";

            ctx.AddSource(
                "BuildInfo.g.cs",
                SourceText.From(source, Encoding.UTF8));
        });
    }
}

この Generator を参照したプロジェクトでは、ソースファイルを書いていなくても、この型を使えるようになります。

Console.WriteLine(Generated.BuildInfo.Tool);

Source Generator は、ユーザーの既存コードを書き換えるものではありません。できることは、追加のソースコードを生成してコンパイルに参加させることです。

したがって、こう考えると分かりやすいです。

既存コードを変換するものではない
既存コードを見て、追加コードを作るもの

既存コードの一括書き換えをしたい場合は、Source Generator ではなく、Roslyn を使った移行ツールや Code Fix を検討します。

18. Source Generatorの参照方法

開発中に Generator プロジェクトを別プロジェクトから参照する場合、通常のライブラリ参照とは扱いが異なります。

生成器は、実行時に参照するライブラリではなく、コンパイル時に Analyzer として読み込まれるものだからです。

プロジェクト参照では、こう指定します。

<ItemGroup>
  <ProjectReference Include="..\BuildInfoGenerator\BuildInfoGenerator.csproj"
                    OutputItemType="Analyzer"
                    ReferenceOutputAssembly="false" />
</ItemGroup>

ReferenceOutputAssembly="false" とすることで、Generator の DLL を通常の参照アセンブリとして扱わないようにします。

NuGet パッケージとして配布する場合も、Analyzer / Source Generator として読み込まれるように配置します。

Source Generator は便利ですが、通常のライブラリとはライフサイクルが違います。

通常のライブラリ: 実行時にアプリから使われる
Source Generator: コンパイル時にコンパイラから使われる

この違いを意識しておくことが大切です。

19. Source Generatorが向いているもの

Source Generator は、何でも生成すればよいというものではありません。

向いているのは、こういうコードです。

人間が書くと退屈で間違いやすい
入力情報から機械的に決まる
生成結果が読みやすい
実行時Reflectionを減らせる
AOTやトリミングと相性を良くできる
型安全性を高められる

例を挙げます。

JSONシリアライズ用のメタデータ
DI登録コード
設定値アクセサ
APIクライアント
enum変換コード
SQLやCSV定義からの型生成
INotifyPropertyChangedの補助コード

ただし、生成されたコードが複雑すぎると、問題が起きたときに追いにくくなります。

Source Generator を使うときは、このあたりを意識するとよいです。

生成コードを確認できるようにする
生成コードの名前を安定させる
生成結果を決定的にする
エラー時のDiagnosticを分かりやすくする
入力が少し変わっただけで大量の差分が出ないようにする

生成コードは、魔法に見えてはいけません。将来の保守者が読めるコードを出すことが重要です。

20. Source Generatorが向いていないもの

Source Generator に向いていない処理もあります。

実行時の状態に依存する処理
ネットワークアクセスが必要な処理
外部サービスの現在値に依存する処理
毎回結果が変わる処理
既存ソースの書き換え
巨大な全ソリューション解析

Generator はコンパイル時に動くため、遅い Generator はビルド時間や IDE の体験を悪化させます。

また、環境依存の Generator は、こうした問題を起こします。

開発者のPCでは通るがCIでは落ちる
CIでは通るが別OSでは落ちる
キャッシュの状態で結果が変わる
ネットワーク障害でビルドが落ちる

Source Generator は、なるべく純粋な処理に寄せるのがよいです。

入力: ソースコード、AdditionalFiles、AnalyzerConfigOptions
出力: 生成C#コード、Diagnostic

この関係が明確であるほど、安定した Generator になります。

21. Roslynを使ったコード調査ツール

Roslyn の用途は、Analyzer や Source Generator だけではありません。自作のコンソールツールから Roslyn を使う方法も実務では有効です。

たとえば、こういう要件があります。

古いAPIの利用箇所を一覧化したい
プロジェクトごとのpublic class数を数えたい
特定の属性が付いたクラスをCSV化したい
巨大ソリューションで依存している名前空間を調べたい
.NET Framework移行前にWindows依存APIを洗い出したい

このような場合、Analyzer として作るより、単発または定期実行の調査ツールとして作った方が扱いやすいことがあります。

例として、ソリューション内の public class を列挙する簡単なイメージです。

using Microsoft.Build.Locator;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.MSBuild;

MSBuildLocator.RegisterDefaults();

using var workspace = MSBuildWorkspace.Create();
var solution = await workspace.OpenSolutionAsync(args[0]);

foreach (var project in solution.Projects)
{
    foreach (var document in project.Documents)
    {
        var root = await document.GetSyntaxRootAsync();
        if (root is null)
        {
            continue;
        }

        var classes = root.DescendantNodes()
            .OfType<ClassDeclarationSyntax>()
            .Where(c => c.Modifiers.Any(m => m.Text == "public"));

        foreach (var cls in classes)
        {
            Console.WriteLine($"{project.Name},{document.FilePath},{cls.Identifier.Text}");
        }
    }
}

この例は構文だけを見ています。もし「特定の基底クラスを継承している public class」を調べたいなら、SemanticModel を使って型の継承関係を見る必要があります。

名前や形だけで十分ならSyntax
型や参照先まで必要ならSemanticModel
プロジェクト全体を扱うならWorkspace

この使い分けが基本です。

22. 正規表現との違い

正規表現は便利ですが、C# コードの意味を扱うには向いていません。

たとえば、このコードを考えます。

// Console.WriteLine("debug");

正規表現で Console.WriteLine を探すと、コメント内の文字列も拾うかもしれません。

こういう文字列リテラルもあります。

var text = "Console.WriteLine";

あるいは、改行を挟むこともできます。

Console
    .WriteLine("Hello");

さらに、別名を使うこともあります。

using C = System.Console;

C.WriteLine("Hello");

正規表現でこれらを正しく扱うのは難しいです。

Roslyn を使えば、コメント、文字列リテラル、構文上のメソッド呼び出し、実際に解決されたメソッドを分けて扱えます。

もちろん、単純な調査なら grepripgrep で十分なこともあります。しかし、結果をもとに設計判断や自動修正をするなら、Roslyn を使った方が安全です。

ざっくり探すだけなら文字列検索
C#として正しく判定したいならRoslyn

23. 既存資産の調査にRoslynを使う

.NET Framework から現行 .NET へ移行するとき、最初に必要なのは「現状把握」です。このとき Roslyn は役に立ちます。

たとえば、こういう調査です。

System.Web 依存の一覧
App.config / Web.config 前提コードの一覧
Windows Forms / WPF 固有APIの利用箇所
Remoting / BinaryFormatter の利用箇所
COM参照の有無
P/Invokeの一覧
非同期化されていないI/O処理
古い暗号APIの利用箇所

単純な文字列検索でも候補は出せます。しかし、Roslyn を使うと、型やメソッドとして解決された結果に基づいて一覧化できます。

たとえば、BinaryFormatter という文字列を探すだけでは、コメントやドキュメントも拾います。

Roslyn で System.Runtime.Serialization.Formatters.Binary.BinaryFormatter 型の利用を調べれば、より正確な候補を出せます。

移行作業では、最初から完璧な Analyzer を作る必要はありません。まずは、調査用のコンソールツールとしてこんな CSV を出せるだけでも価値があります。

Project,File,Line,Symbol,Kind
Legacy.Web,Controllers/HomeController.cs,42,System.Web.HttpContext.Current,Property
Legacy.Core,Serialization/OldStore.cs,18,System.Runtime.Serialization.Formatters.Binary.BinaryFormatter,Type

このような一覧があると、移行計画を立てやすくなります。

24. ライブラリ開発者にとってのRoslyn

Roslyn は、アプリケーション開発者だけでなく、ライブラリ開発者にも役立ちます。ライブラリには、正しい使い方があります。

たとえば、こういうルールです。

初期化メソッドを先に呼ぶ必要がある
特定の属性を付ける必要がある
Disposeを呼ぶ必要がある
特定のオプション指定が危険である
非推奨APIを新規コードで使ってほしくない

これらをドキュメントだけで伝えると、利用者が読み落とすことがあります。

Analyzer をライブラリの NuGet パッケージに同梱すれば、利用者のコード上で警告を出せます。

たとえば、自社ライブラリ Company.Messaging があるとして、こういう誤用を検出できます。

var client = new MessageClient();
client.Send(message); // Configureを呼ぶ前にSendしている

Analyzer はこういう警告を出せます。

CMP1001: MessageClient.Sendを呼び出す前にConfigureを呼び出してください

さらに Code Fix で、修正候補やサンプルコードを提示できます。これは、ライブラリの利用体験を改善します。

ドキュメントに書いてあることを、利用者のエディタ上で伝える

この発想は、Roslyn の大きな価値です。

25. AnalyzerをNuGetで配るときの考え方

Analyzer は NuGet パッケージとして配れます。ただし、通常の実行時ライブラリとは分けて考える必要があります。Analyzer はアプリケーションの実行時に必要なものではなく、ビルド時や IDE 上で使われるものだからです。

そのため、パッケージ設計ではこうした点を考えます。

実行時ライブラリとAnalyzerを同じパッケージに含めるか
Analyzerを別パッケージにするか
既定で警告を出すか
重大度をどうするか
.editorconfigで制御できるようにするか
既存利用者に突然大量警告を出さないか

自社内で使うなら、ある程度強いルールでも受け入れやすいかもしれません。

公開ライブラリとして配るなら、利用者のビルドを突然壊さない配慮が必要です。

最初は InfoWarning から始め、必要に応じて利用者側で Error に上げられるようにする方が扱いやすいことが多いです。

26. RoslynとIDE機能

Visual Studio やその他の .NET 開発環境では、Roslyn の考え方が IDE 機能にも深く関わっています。

たとえば、こうした機能です。

IntelliSense
Go to Definition
Find All References
Rename
Extract Method
Quick Actions
コードスタイル警告
未使用usingの検出

これらは、単なる文字列検索では実現できません。たとえば Rename では、同じ名前の別シンボルを誤って変更してはいけません。

class User
{
    public string Name { get; set; }
}

class Product
{
    public string Name { get; set; }
}

User.Name を変更したいとき、Product.Name まで変えてはいけません。これは、構文だけでなく、シンボルとして区別する必要があります。

Roslyn の API は、このような IDE 的な機能を自作ツールにも応用できる土台になります。

27. パフォーマンス上の注意

Roslyn は強力ですが、重い処理を書けば当然遅くなります。特に Analyzer と Source Generator は、開発者の入力中やビルド中に動く可能性があります。

そのため、このあたりに注意します。

不要なSemanticModel取得を避ける
Syntaxで候補を絞ってから意味解析する
ファイルI/Oを避ける
ネットワークアクセスをしない
重いReflectionを避ける
キャンセル要求を尊重する
並列実行を意識する
全ソリューション解析をAnalyzerに持ち込まない

Analyzer では、Initialize で登録する対象をできるだけ絞ります。

悪い例です。

すべてのSyntaxNodeを見てから、内部で大量のif文で判定する

よい方向性です。

必要なSyntaxKindだけを登録する
まず名前や形で軽く絞る
必要な場合だけSemanticModelで確定する

Analyzer は、利用者の開発環境に常駐する可能性があります。そのため、正確さだけでなく、軽さも品質です。

28. Diagnosticの設計

Analyzer で出す Diagnostic は、単に警告を出せばよいわけではありません。開発者が見たときに、こうしたことが分かる必要があります。

何が問題なのか
なぜ問題なのか
どこを直せばよいのか
どう直せばよいのか
例外はあるのか

悪いメッセージの例です。

CMP001: 禁止されています

これでは、何が悪いのか分かりません。

よい方向性の例です。

CMP001: DateTime.Nowは実行環境のローカル時刻に依存します。保存・比較用の時刻にはDateTimeOffset.UtcNowまたは時刻プロバイダーを使ってください。

Diagnostic ID も設計しておくとよいです。

CMP0001-CMP0999: 共通ルール
CMP1000-CMP1999: ライブラリAのルール
CMP2000-CMP2999: 移行支援ルール

ドキュメントページを用意できる場合は、DiagnosticDescriptor に HelpLinkUri を設定するのも有効です。

警告は、開発者へのコミュニケーションです。メッセージが雑だと、ルール自体も信用されなくなります。

29. 重大度の設計

Analyzer の重大度は慎重に決めます。代表的な段階はこうです。

Hidden / Silent
Info
Suggestion
Warning
Error

実務では、いきなり Error にしない方がよいことが多いです。特に既存コードが多い場合、最初から Error にすると導入が止まります。

現実的には、こういう段階導入がしやすいです。

1. まずWarningで導入する
2. CIで警告数を可視化する
3. 新規違反を増やさない
4. 重要ルールだけErrorにする
5. 既存違反を減らす計画を立てる

Analyzer の目的は、開発者を困らせることではなく、コードベースの品質を無理なく上げることです。

30. Source Generatorのデバッグ

Source Generator は、普通のアプリケーションとは実行される場所が違うため、デバッグに少し癖があります。

基本的には、こうした方法で調査します。

生成されたソースを見る
Diagnosticを出す
テストを書く
必要ならデバッガをアタッチする

SDK スタイルのプロジェクトでは、生成ファイルを出力する設定を使うと確認しやすくなります。

<PropertyGroup>
  <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
  <CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)Generated</CompilerGeneratedFilesOutputPath>
</PropertyGroup>

これにより、生成された .g.cs を確認しやすくなります。

$(BaseIntermediateOutputPath) は、通常 obj/ 配下を指します。

Generated のようにプロジェクト直下を指定すると、SDK スタイルのプロジェクトでは既定で **/*.cs がコンパイル対象に含まれるため、次回ビルドで生成済みの .g.cs が通常のソースとして再度取り込まれ、型やメンバーの重複エラーになることがあります。

どうしてもプロジェクト直下に出力する場合は、<Compile Remove="Generated/**/*.cs" /> のように明示的にコンパイル対象から除外します。

Generator のテストでは、入力コードと生成結果を比較する形がよく使われます。

入力ソースを用意する
Generatorを実行する
生成されたソースを確認する
期待するDiagnosticを確認する

Source Generator は、手作業で動作確認するだけではすぐに壊れます。生成ロジックが複雑になるほど、テストが重要です。

31. Roslynを使ったテスト

Analyzer や Source Generator は、テストを書いて育てるべきです。特に Analyzer は、誤検出と検出漏れの両方が問題になります。

テストでは、こうしたパターンを用意します。

検出すべきコード
検出してはいけないコード
using aliasを使ったコード
完全修飾名を使ったコード
似た名前の別型を使ったコード
generated code扱いのコード
nullable有効時のコード

たとえば、System.DateTime.Now を禁止する Analyzer なら、こういうケースを確認します。

// 検出すべき
var x = System.DateTime.Now;
// usingがある場合も検出すべき
using System;
var x = DateTime.Now;
// 別型なら検出してはいけない
namespace MyCompany;

public static class DateTime
{
    public static string Now => "now";
}

var x = DateTime.Now;

この最後のケースは、文字列検索では間違えやすい例です。Roslyn Analyzer では、SemanticModel によって対象シンボルを確認することで避けられます。

32. .NET Frameworkプロジェクトでも使えるのか

Roslyn は、現行 .NET だけのものではありません。ただし、「どう使うか」によって注意点が変わります。

調査ツールとして使う場合

.NET 8 や .NET 10 のコンソールアプリとして Roslyn ツールを作り、.NET Framework のソリューションを読み込んで解析することは現実的な選択肢です。

この場合、ツール自体は現行 .NET で動かし、解析対象は .NET Framework のコードにできます。

ただし、MSBuildWorkspace でソリューションを読み込む場合は、対象プロジェクトをビルドできる MSBuild、SDK、参照アセンブリ、NuGet 復元環境が必要です。

つまり、Roslyn だけで何でも読めるわけではなく、実際のプロジェクト構成を解決するにはビルド環境が必要です。

Analyzerとして使う場合

Analyzer は、コンパイラや IDE に読み込まれて動きます。

対象プロジェクトが .NET Framework でも、コンパイラが Analyzer を読み込める環境なら利用できます。

ただし、古い csproj、古い Visual Studio、古い MSBuild、packages.config 前提の構成では、導入や運用が現行 SDK スタイルほど素直ではないことがあります。

既存の .NET Framework プロジェクトに導入する場合は、まずこのあたりを確認するとよいです。

Visual Studio / MSBuild のバージョン
PackageReferenceを使えるか
CIで同じAnalyzerが動くか
警告がビルドログに出るか
.editorconfigが効くか

Source Generatorとして使う場合

Source Generator は、コンパイル時にコンパイラが読み込む仕組みです。

そのため、対象プロジェクトの実行時フレームワークよりも、ビルドに使うコンパイラや SDK の対応状況が重要です。

現行 .NET の SDK スタイルプロジェクトでは扱いやすい一方、古い .NET Framework プロジェクトでは、プロジェクト形式やビルド環境によって注意が必要です。

.NET Framework の既存資産に対しては、最初から Source Generator を組み込むより、まずは Roslyn ベースの調査ツールや Analyzer から始める方が安全なことが多いです。

33. バージョン選定の注意

Roslyn 関連の NuGet パッケージには、Microsoft.CodeAnalysis.* が含まれます。

代表的にはこのあたりです。

Microsoft.CodeAnalysis.CSharp
Microsoft.CodeAnalysis.CSharp.Workspaces
Microsoft.CodeAnalysis.Workspaces.MSBuild
Microsoft.CodeAnalysis.Analyzers
Microsoft.CodeAnalysis.CSharp.CodeFix.Testing
Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing

ここで注意したいのは、Analyzer や Source Generator は、利用者側のコンパイラに読み込まれるという点です。

つまり、開発者の手元や CI の SDK / Visual Studio が古いと、新しすぎる Roslyn API を使った Analyzer / Generator が動かないことがあります。

社内専用でビルド環境を統一できるなら、比較的新しい API を使いやすいです。

一方で、外部配布ライブラリでは、広い利用者環境を考えて、依存する Microsoft.CodeAnalysis のバージョンを慎重に選ぶ必要があります。

方針としては、こう考えるとよいです。

社内専用: CIと開発環境をそろえたうえで新しめのAPIを使う
外部配布: 利用者のSDK/VS範囲を考えて保守的に選ぶ
Generator: 可能ならIncremental Generatorとして設計する
Analyzer: IDE体験を壊さない軽さを優先する

Roslyn はコンパイラに近い領域なので、バージョン差の影響を受けやすいです。

34. Roslynで何でもやろうとしない

Roslyn は強力ですが、すべての問題を解く道具ではありません。たとえば、こういう問題は Roslyn だけでは解けません。

実行時にどの分岐を通るか
本番データでどの値が来るか
リフレクションで動的に呼ばれるメソッド
DIコンテナの実行時登録結果
設定ファイルによって変わる処理
外部サービスから返る値

Roslyn は、主にソースコードとコンパイル情報を扱う道具です。実行時のふるまいを知りたい場合は、テスト、ログ、トレース、プロファイリング、ダンプ解析など別の手段が必要です。

したがって、Roslyn の役割はこう考えるとよいです。

静的に分かることを高い精度で扱う

動的にしか分からないことまで Roslyn で無理に解こうとすると、複雑で不正確な仕組みになります。

35. 導入の順番

実務で Roslyn を使い始めるなら、この順番がおすすめです。

1. 既存の .NET Analyzer と .editorconfig を整える
2. Syntax Tree を使った小さな調査ツールを書く
3. SemanticModel を使って型解決を試す
4. MSBuildWorkspace でソリューションを読む
5. チーム固有の小さな Analyzer を作る
6. 必要なら Code Fix を追加する
7. 定型コードが多い箇所に Source Generator を検討する

最初から Source Generator に行く必要はありません。多くの現場では、まず Analyzer と調査ツールの方が効果を出しやすいです。

特に既存資産が大きい場合は、こういう流れが現実的です。

調査ツールで現状を把握する
頻出する問題をAnalyzer化する
安全に直せるものだけCode Fix化する
繰り返し書いている定型コードをGenerator化する

Roslyn は、段階的に使える道具です。

36. 小さなサンプル: メソッド呼び出しを一覧化する

最後に、Roslyn の使い方をもう少し実務に近い形で見てみます。ここでは、ソリューション内のメソッド呼び出しを一覧化するイメージです。

using Microsoft.Build.Locator;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.MSBuild;

MSBuildLocator.RegisterDefaults();

using var workspace = MSBuildWorkspace.Create();
var solution = await workspace.OpenSolutionAsync(args[0]);

foreach (var project in solution.Projects)
{
    var compilation = await project.GetCompilationAsync();
    if (compilation is null)
    {
        continue;
    }

    foreach (var document in project.Documents)
    {
        var tree = await document.GetSyntaxTreeAsync();
        if (tree is null)
        {
            continue;
        }

        var root = await tree.GetRootAsync();
        var semanticModel = compilation.GetSemanticModel(tree);

        var invocations = root
            .DescendantNodes()
            .OfType<InvocationExpressionSyntax>();

        foreach (var invocation in invocations)
        {
            var symbol = semanticModel.GetSymbolInfo(invocation).Symbol as IMethodSymbol;
            if (symbol is null)
            {
                continue;
            }

            var lineSpan = invocation.GetLocation().GetLineSpan();
            var line = lineSpan.StartLinePosition.Line + 1;

            Console.WriteLine(string.Join(",", new[]
            {
                project.Name,
                document.FilePath ?? document.Name,
                line.ToString(),
                symbol.ContainingType.ToDisplayString(),
                symbol.Name
            }));
        }
    }
}

このようなツールを少し拡張すると、こうした調査ができます。

特定メソッドの呼び出しだけ抽出する
非推奨APIの利用箇所を出す
プロジェクトごとの利用頻度を出す
移行対象APIの一覧を作る

ソースコードをコンパイラの視点で読めると、既存コードの調査がかなり楽になります。

37. Roslynでコードを書き換えるときの注意

Roslyn では、構文木を使ってコードを書き換えることもできます。

たとえば、特定のメソッド名を変えたり、属性を追加したり、using を追加したりできます。

ただし、コード書き換えは慎重に行うべきです。注意点はこのあたりです。

意味が変わらないことを確認する
コメントや空白を壊さない
差分が大きくなりすぎない
フォーマットを統一する
一度に多くの変換をしすぎない
Git差分をレビューしやすくする

Roslyn の Syntax Tree は Trivia を保持するため、コメントや空白を維持した変換が可能です。しかし、雑にノードを作ると、生成されたコードの整形が崩れることがあります。

書き換えツールを作るときは、この方針が安全です。

まず検出だけを行う
変換前後の差分を確認する
小さな変換から始める
変換ツール自体にテストを書く
CIでは検出モードから始める

大規模な機械変換では、Roslyn は強力ですが、最後は人間のレビューが必要です。

38. RoslynとAIコーディング支援

近年は、AI によるコード生成やレビュー支援も一般的になっていますが、その中でも Roslyn の価値は下がりません。AI は自然言語や周辺文脈を扱うのが得意で、Roslyn はコンパイラとして正確な構文・意味情報を扱うのが得意です。両者は競合というより補完関係です。

たとえば、こういう使い分けが考えられます。

Roslynで正確に対象箇所を抽出する
AIで修正方針や説明文を生成する
Roslynで修正案がコンパイル可能か確認する
Analyzerで再発を防ぐ

AI に「このコードベースの古いAPIを全部直して」と頼むより、Roslyn で対象箇所を正確に抽出した方が安全な場合があります。

そして、修正方針の検討やレビュー補助に AI を使うと、より実務的です。

コンパイラが分かることはコンパイラに任せる。 人間や AI は、その上の判断に集中する。

この分担が重要です。

39. 実務チェックリスト

Roslyn を使う前に、このあたりを確認するとよいです。

目的は調査か、警告か、修正か、生成か
構文だけで足りるか、意味解析が必要か
単一ファイルでよいか、プロジェクト全体が必要か
IDE上で動かす必要があるか、単発ツールでよいか
ビルド時間に影響してもよいか
CIで実行するか
既存コードに警告が大量発生しないか
Analyzerの重大度をどうするか
Code Fixは安全に適用できるか
Source Generatorの生成コードを確認できるか
利用者のSDK / Visual Studioバージョンは揃っているか

判断に迷ったら、こう分けるとよいです。

調査したい            -> Roslynを使ったコンソールツール
常に守らせたい        -> Analyzer
直し方が決まっている  -> Code Fix
定型コードを作りたい  -> Source Generator

この分け方をすると、Roslyn の使いどころを間違えにくくなります。

40. まとめ

Roslyn は、C# や Visual Basic のコンパイラを、開発者が利用できる API として開いたものです。

Roslyn を使うと、ソースコードを単なる文字列ではなく、こうした形で扱えます。

Syntax Treeとして構文を読む
SemanticModelとして意味を読む
Compilationとしてコンパイル全体を扱う
Workspaceとしてソリューションやプロジェクトを扱う
Analyzerとして警告を出す
Code Fixとして修正案を提示する
Source Generatorとしてコードを生成する

実務では、こういう場面で特に役立ちます。

既存コードベースの調査
.NET Frameworkから.NETへの移行支援
チーム規約の自動チェック
ライブラリ利用者へのガイド
定型コードの生成
IDEやCIでの品質担保

大事なのは、Roslyn を「難しいコンパイラ技術」として構えすぎないことです。

最初は、1 ファイルを CSharpSyntaxTree.ParseText で読み、メソッド名を列挙するだけで十分です。そこから、SemanticModel、Workspace、Analyzer、Source Generator と広げていけばよいです。

Roslyn をひとことで言うなら、こうなります。

C# コードを、文字列ではなく、コンパイラの理解した構造として扱えるようにする。

この視点を持つと、コードレビュー、移行、調査、生成の自動化が一段やりやすくなります。

参考

同じタグを共有する最新の記事です。さらに近い話題で知識を深められます。

このテーマと近いトピックページです。記事を起点に、関連するサービスや他の記事へ進めます。

この記事は次のサービスページにつながります。近い入口からご覧ください。

著者プロフィール

記事の著者プロフィールページです。

小村 豪

合同会社小村ソフト 代表

Windows ソフト開発、技術相談、不具合調査を中心に、既存資産が残る案件や原因が見えにくい障害調査に強みがあります。

ブログ一覧に戻る