先決條件
簡介
今天,Visual Basic和C#編譯器是黑盒子:輸入文本然后輸出字節,編譯管道的中間階段沒有透明性。使用.NET編譯器平台(以前稱為“Roslyn”),工具和開發人員可以利用編譯器使用的完全相同的數據結構和算法來分析和理解代碼。
本篇文章,我們將探索Symbol和BindingAPI。通過語法API來查看解析器,語法樹,用於推理和構造它們的實用程序。
理解編譯和符號
這個語法API能讓你看程序的結構。但是,通常您需要有關程序語義或含義的更豐富信息。雖然松散的代碼片段可以單獨進行語法分析,但孤立的提出諸如“這個變量的類型是什么”之類的問題並不是很有意義。類型名稱的含義可能取決於程序集引用,命名空間導入或其他代碼文件。這就是Compilation類的用武之地。
編譯類似於編譯器看到的單個項目,表示編譯Visual Basic或C#程序所需的所有內容,例如程序集引用,編譯器選項和要編譯的源文件集。 通過此上下文,您可以推斷出代碼的含義。 編譯允許您查找符號 - 名稱和其他表達式引用的類型,名稱空間,成員和變量等實體。 將名稱和表達式與符號(Symbols)相關聯的過程稱為Binding。
與SyntaxTree一樣,Compilation是一個具有特定語言派生類的抽象類。創建Compilation實例時,必須在CSharpCompilation(或VisualBasicCompilation)類上調用工廠方法。
演示-創建編譯
- 引入Nuget
Microsoft.CodeAnalysis.CSharp
Microsoft.CodeAnalysis.CSharp.Workspaces
- 上節提到的演示Main代碼
class Program
{
static void Main(string[] args)
{
SyntaxTree tree = CSharpSyntaxTree.ParseText(
@"using System;
using System.Collections.Generic;
using System.Text;
namespace HelloWorld
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(""Hello, World!"");
}
}
}");
var root = (CompilationUnitSyntax)tree.GetRoot();
}
}
- 接下來,在Main方法的末尾創建CSharpCompilation對象
var compilation = CSharpCompilation.Create("HelloWorld")
.AddReferences(
MetadataReference.CreateFromFile(
typeof(object).Assembly.Location))
.AddSyntaxTrees(tree);
- 設置斷點,啟動調試,在compilation處查看提示。
語義模型SemanticModel
一旦你有了編譯,你可以要求它為該編譯中包含的任何SyntaxTree提供SemanticModel。你可以查詢SemanticModel來回答諸如“這個位置的范圍是什么名稱?”,“從這種方法可以獲得哪些成員?” ,“在這個文本塊中使用了哪些變量?”和“這個名字/表達是指什么?”之類的問題。
示例-綁定名稱
此示例顯示如何為HelloWorld SyntaxTree獲取SemanticModel對象。獲得SemanticModel后,第一個using指令中的名稱綁定為System命名空間的符號。
- 將下段代碼放到Main的末尾。
var model = compilation.GetSemanticModel(tree);
var nameInfo = model.GetSymbolInfo(root.Usings[0].Name);
var systemSymbol = (INamespaceSymbol)nameInfo.Symbol;
*追加以下代碼,枚舉System命名空間的子命名空間並將其名稱打印到控制台:
foreach (var ns in systemSymbol.GetNamespaceMembers())
{
Console.WriteLine(ns.Name);
}
- Debug進入調試,查看每個節點的值。Console輸出結果如下:
Buffers
Collections
ComponentModel
Configuration
Diagnostics
Globalization
IO
Numerics
Reflection
Resources
Runtime
Security
StubHelpers
Text
Threading
示例--綁定表達式
前面的示例顯示了如何綁定name去查找Symbol。但是,在C#程序中還有其他不是Name的表達式可以綁定。此示例顯示綁定如何與其他表達式類型一起使用 - 在本例中為簡單的字符串文字。
var helloWorldString = root.DescendantNodes()
.OfType<LiteralExpressionSyntax>()
.First();
var literalInfo = model.GetTypeInfo(helloWorldString);
var stringTypeSymbol = (INamedTypeSymbol)literalInfo.Type;
Console.Clear();
foreach (var name in (from method in stringTypeSymbol.GetMembers()
.OfType<IMethodSymbol>()
where method.ReturnType.Equals(stringTypeSymbol) &&
method.DeclaredAccessibility ==
Accessibility.Public
select method.Name).Distinct())
{
Console.WriteLine(name);
}
- 運行Debug,查看相關節點的值,Console輸出結果如下
Intern
IsInterned
Create
Copy
ToString
Normalize
Concat
Format
Insert
Join
PadLeft
PadRight
Remove
Replace
Substring
ToLower
ToLowerInvariant
ToUpper
ToUpperInvariant
Trim
TrimStart
TrimEnd
總結
本篇文章演示了語義分析,通過兩個示例分別演示綁定Name查找Symbol和綁定表達式 我們可以獲得以下幾個知識點:
獲取語法樹的根節點:(CompilationUnitSyntax)tree.GetRoot()
用於創建CSharpCompilation對象:CSharpCompilation.Create("HelloWorld").AddReferences( MetadataReference.CreateFromFile( typeof(object).Assembly.Location)) .AddSyntaxTrees(tree)
用於獲取模型:compilation.GetSemanticModel(tree);
獲取Name的Symbol信息: model.GetSymbolInfo(root.Usings[0].Name);
可獲取具體命名空間名:(INamespaceSymbol)nameInfo.Symbol;
獲取當前命名空間下所有成員:systemSymbol.GetNamespaceMembers()
獲取文字表達式:root.DescendantNodes().OfType<LiteralExpressionSyntax>()
獲取Type信息 model.GetTypeInfo(helloWorldString)
獲取具體的Type類型 (INamedTypeSymbol)literalInfo.Type;
獲取返回值是string,公開類型的方法: from method in stringTypeSymbol.GetMembers().OfType<IMethodSymbol>() where method.ReturnType.Equals(stringTypeSymbol) && method.DeclaredAccessibility == Accessibility.Public select method.Name