先决条件
简介
今天,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()
获取Type信息 model.GetTypeInfo(helloWorldString)
获取具体的Type类型 (INamedTypeSymbol)literalInfo.Type;
获取返回值是string,公开类型的方法:
from method in stringTypeSymbol.GetMembers().OfType()
where method.ReturnType.Equals(stringTypeSymbol) &&
method.DeclaredAccessibility ==
Accessibility.Public
select method.Name