このチュートリアルでは、Syntax API について理解していることを前提としています。 構文解析を始めるための記事では、十分な導入を提供します。
このチュートリアルでは、 シンボル API と バインド API について説明します。 これらの API は、プログラムの セマンティックの意味 に関する情報を提供します。 プログラム内の任意のシンボルによって表される型に関する質問と回答を行えます。
.NET Compiler Platform SDK をインストールする必要があります。
インストール手順 - Visual Studio インストーラー
Visual Studio インストーラーで .NET コンパイラ プラットフォーム SDK を見つけるには、次の 2 つの方法があります。
Visual Studio インストーラー - ワークロード ビューを使用してインストールする
.NET Compiler Platform SDK は、Visual Studio 拡張機能開発ワークロードの一部として自動的に選択されません。 オプションコンポーネントとして選択する必要があります。
- Visual Studio インストーラーを実行する
- [変更] を選択する
- Visual Studio 拡張機能の開発ワークロードを確認します。
- 概要ツリーで Visual Studio 拡張機能開発 ノードを開きます。
- .NET コンパイラ プラットフォーム SDK のボックスがオンになっていることを確認します。
- [変更] を選択します。
必要に応じて、 DGML エディター でビジュアライザーにグラフを表示することもできます。
- 概要ツリーで [ 個々のコンポーネント ] ノードを開きます。
- DGML エディターのチェック ボックスをオンにします
Visual Studio インストーラーを使用したインストール - [個々のコンポーネント] タブ
- Visual Studio インストーラーを実行する
- [変更] を選択する
- [ 個々のコンポーネント ] タブを選択する
- .NET コンパイラ プラットフォーム SDK のチェック ボックスをオンにします。 上部の [ コンパイラ、ビルド ツール、およびランタイム ] セクションにあります。
- [変更] を選択します。
必要に応じて、 DGML エディター でビジュアライザーにグラフを表示することもできます。
- DGML エディターのチェック ボックスをオンにします。 [ コード ツール ] セクションに表示されます。
コンパイルとシンボルについて
.NET Compiler SDK を使い始めるにつれて、Syntax API と Semantic API の違いをよく理解できるようになります。 Syntax API を使用すると、プログラムの構造を確認できます。 ただし、多くの場合、プログラムのセマンティクスまたは 意味 に関する豊富な情報が必要です。 緩いコード ファイルまたは Visual Basic または C# コードのスニペットは、分離して構文的に分析できますが、バキュームで "この変数の型は何ですか" などの質問をすることは意味がありません。 型名の意味は、アセンブリ参照、名前空間のインポート、またはその他のコード ファイルによって異なる場合があります。 これらの質問には、 セマンティック API (具体的には Microsoft.CodeAnalysis.Compilation クラス) を使用して回答します。
Compilationのインスタンスは、コンパイラによって表示される 1 つのプロジェクトに似ています。Visual Basic または C# プログラムのコンパイルに必要なすべてのものを表します。 コンパイルには、 コンパイル するソース ファイルのセット、アセンブリ参照、およびコンパイラ オプションが含まれます。 このコンテキストの他のすべての情報を使用して、コードの意味を理由にすることができます。 Compilationを使用すると、シンボル (型、名前空間、メンバー、名前やその他の式が参照する変数などのエンティティ) を検索できます。 名前と式を シンボル に関連付けするプロセスは、 Binding と呼ばれます。
Microsoft.CodeAnalysis.SyntaxTreeと同様に、Compilationは言語固有の派生物を持つ抽象クラスです。 Compilation のインスタンスを作成するときは、 Microsoft.CodeAnalysis.CSharp.CSharpCompilation (または Microsoft.CodeAnalysis.VisualBasic.VisualBasicCompilation) クラスでファクトリ メソッドを呼び出す必要があります。
シンボルのクエリ
このチュートリアルでは、"Hello World" プログラムをもう一度見てみましょう。 今回は、プログラム内のシンボルに対してクエリを実行して、それらのシンボルが表す型を理解します。 名前空間内の型に対してクエリを実行し、型で使用可能なメソッドを見つける方法を学習します。
このサンプルの完成したコードは 、GitHub リポジトリで確認できます。
注
構文ツリー型は、継承を使用して、プログラム内のさまざまな場所で有効なさまざまな構文要素を記述します。 これらの API を使用することは、多くの場合、プロパティまたはコレクション メンバーを特定の派生型にキャストすることを意味します。 次の例では、代入とキャストは、明示的に型指定された変数を使用して個別のステートメントです。 コードを読み取って、API の戻り値の型と、返されるオブジェクトのランタイム型を確認できます。 実際には、暗黙的に型指定された変数を使用し、API 名を使用して調べるオブジェクトの種類を記述する方が一般的です。
新しい C# Stand-Alone Code Analysis Tool プロジェクトを作成します。
- Visual Studio で、[ ファイル>新規作成>プロジェクト ] を選択して、[新しいプロジェクト] ダイアログを表示します。
- Visual C#>Extensibility で、Stand-Alone コード分析ツールを選択します。
- プロジェクトに "SemanticQuickStart" という名前を付け、[OK] をクリックします。
前に示した基本的な "Hello World!" プログラムを分析します。
Hello World プログラムのテキストを定数として Program クラスに追加します。
const string programText =
@"using System;
using System.Collections.Generic;
using System.Text;
namespace HelloWorld
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(""Hello, World!"");
}
}
}";
次に、次のコードを追加して、 programText 定数内のコード テキストの構文ツリーをビルドします。
Main メソッドに次の行を追加します。
SyntaxTree tree = CSharpSyntaxTree.ParseText(programText);
CompilationUnitSyntax root = tree.GetCompilationUnitRoot();
次に、既に作成したツリーから CSharpCompilation を作成します。 "Hello World" サンプルは、 String 型と Console 型に依存しています。 コンパイルでこれら 2 つの型を宣言するアセンブリを参照する必要があります。
Main メソッドに次の行を追加して、適切なアセンブリへの参照を含む構文ツリーのコンパイルを作成します。
var compilation = CSharpCompilation.Create("HelloWorld")
.AddReferences(MetadataReference.CreateFromFile(
typeof(string).Assembly.Location))
.AddSyntaxTrees(tree);
CSharpCompilation.AddReferences メソッドは、コンパイルへの参照を追加します。 MetadataReference.CreateFromFile メソッドは、アセンブリを参照として読み込みます。
セマンティック モデルのクエリ
Compilationを取得したら、Compilationに含まれているSyntaxTreeについてSemanticModelを要求できます。 セマンティック モデルは、通常 IntelliSense から取得するすべての情報のソースと考えることができます。 SemanticModelは、"この場所のスコープ内の名前"、"このメソッドからアクセスできるメンバー"、"このテキスト ブロックで使用される変数"、"この名前/式は何を参照していますか?" などの質問に答えることができます。セマンティック モデルを作成するには、次のステートメントを追加します。
SemanticModel model = compilation.GetSemanticModel(tree);
名前のバインド
Compilationは、SemanticModelからSyntaxTreeを作成します。 モデルを作成したら、それをクエリして最初の using ディレクティブを検索し、 System 名前空間のシンボル情報を取得できます。
Main メソッドに次の 2 行を追加してセマンティック モデルを作成し、最初の using ディレクティブのシンボルを取得します。
// Use the syntax tree to find "using System;"
UsingDirectiveSyntax usingSystem = root.Usings[0];
NameSyntax systemName = usingSystem.Name;
// Use the semantic model for symbol information:
SymbolInfo nameInfo = model.GetSymbolInfo(systemName);
上記のコードは、最初のusing ディレクティブの名前をバインドして、Microsoft.CodeAnalysis.SymbolInfo名前空間のSystemを取得する方法を示しています。 上記のコードは、 構文モデル を使用してコードの構造を見つけることも示しています。セ マンティック モデル を使用してその意味を理解します。
構文モデルは、System ディレクティブ内の文字列usingを検索します。
セマンティック モデルには、System名前空間で定義されている型に関するすべての情報があります。
SymbolInfo オブジェクトから、Microsoft.CodeAnalysis.ISymbol プロパティを使用してSymbolInfo.Symbolを取得できます。 このプロパティは、この式が参照するシンボルを返します。 何も参照しない式 (数値リテラルなど) の場合、このプロパティは null。
SymbolInfo.Symbolが null でない場合、ISymbol.Kindはシンボルの型を表します。 この例では、 ISymbol.Kind プロパティは SymbolKind.Namespaceです。
Main メソッドに次のコードを追加します。
System名前空間のシンボルを取得し、System名前空間で宣言されているすべての子名前空間を表示します。
var systemSymbol = (INamespaceSymbol?)nameInfo.Symbol;
if (systemSymbol?.GetNamespaceMembers() is not null)
{
foreach (INamespaceSymbol ns in systemSymbol?.GetNamespaceMembers()!)
{
Console.WriteLine(ns);
}
}
プログラムを実行すると、次の出力が表示されます。
System.Collections
System.Configuration
System.Deployment
System.Diagnostics
System.Globalization
System.IO
System.Numerics
System.Reflection
System.Resources
System.Runtime
System.Security
System.StubHelpers
System.Text
System.Threading
Press any key to continue . . .
注
出力には、 System 名前空間の子名前空間であるすべての名前空間が含まれているわけではありません。 このコンパイルに存在するすべての名前空間が表示されます。この名前空間は、 System.String が宣言されているアセンブリのみを参照します。 他のアセンブリで宣言された名前空間は、このコンパイルでは認識されません
式のバインド
上記のコードは、名前にバインドしてシンボルを検索する方法を示しています。 C# プログラムには、名前ではないバインドできる式が他にもあります。 この機能を示すために、単純な文字列リテラルへのバインドにアクセスしてみましょう。
"Hello World" プログラムには、 Microsoft.CodeAnalysis.CSharp.Syntax.LiteralExpressionSyntax、コンソールに表示される "Hello, World!" 文字列が含まれています。
"Hello, World!" 文字列は、プログラム内で単一の文字列リテラルを検索することで見つかります。 次に、構文ノードを見つけて、セマンティック モデルからそのノードの型情報を取得します。
Main メソッドに次のコードを追加します。
// Use the syntax model to find the literal string:
LiteralExpressionSyntax helloWorldString = root.DescendantNodes()
.OfType<LiteralExpressionSyntax>()
.Single();
// Use the semantic model for type information:
TypeInfo literalInfo = model.GetTypeInfo(helloWorldString);
Microsoft.CodeAnalysis.TypeInfo構造体には、リテラルの型に関するセマンティック情報へのアクセスを可能にするTypeInfo.Type プロパティが含まれています。 この例では、 string 型です。 このプロパティをローカル変数に割り当てる宣言を追加します。
var stringTypeSymbol = (INamedTypeSymbol?)literalInfo.Type;
このチュートリアルを完了するには、stringを返すstring型で宣言されているすべてのパブリック メソッドのシーケンスを作成する LINQ クエリを作成します。 このクエリは複雑になるため、1 行ずつ構築し、1 つのクエリとして再構築しましょう。 このクエリのソースは、 string 型で宣言されているすべてのメンバーのシーケンスです。
var allMembers = stringTypeSymbol?.GetMembers();
そのソース シーケンスには、プロパティとフィールドを含むすべてのメンバーが含まれているため、 ImmutableArray<T>.OfType メソッドを使用してフィルター処理し、オブジェクト Microsoft.CodeAnalysis.IMethodSymbol 要素を検索します。
var methods = allMembers?.OfType<IMethodSymbol>();
次に、別のフィルターを追加して、パブリックなメソッドのみを返し、 stringを返します。
var publicStringReturningMethods = methods?
.Where(m => SymbolEqualityComparer.Default.Equals(m.ReturnType, stringTypeSymbol) &&
m.DeclaredAccessibility == Accessibility.Public);
name プロパティのみを選択し、オーバーロードを削除して個別の名前のみを選択します。
var distinctMethods = publicStringReturningMethods?.Select(m => m.Name).Distinct();
LINQ クエリ構文を使用して完全なクエリを作成し、コンソールにすべてのメソッド名を表示することもできます。
foreach (string name in (from method in stringTypeSymbol?
.GetMembers().OfType<IMethodSymbol>()
where SymbolEqualityComparer.Default.Equals(method.ReturnType, stringTypeSymbol) &&
method.DeclaredAccessibility == Accessibility.Public
select method.Name).Distinct())
{
Console.WriteLine(name);
}
プログラムをビルドして実行します。 次の出力が表示されます。
Join
Substring
Trim
TrimStart
TrimEnd
Normalize
PadLeft
PadRight
ToLower
ToLowerInvariant
ToUpper
ToUpperInvariant
ToString
Insert
Replace
Remove
Format
Copy
Concat
Intern
IsInterned
Press any key to continue . . .
セマンティック API を使用して、このプログラムの一部であるシンボルに関する情報を検索して表示しました。
.NET