.NET Compiler Platform SDK
.NET Compiler Platform SDK
.NET Compiler Platform 是什么?
通过学习该模型可以更快的了解Roslyn,或者说更快的了解c#编译器的相关知识。
编译器管道
编译器管道是什么
如上图所示,编译器管在每个阶段会进行不同的操作,这些操作我们可以理解为一个独立的组件或者模块,是一个黑盒结构。
-
Parser 分析模块
转化为编译器能读懂的语言语法,遵循编译器的规则解析代码,这里会判断是否有编译问题。
-
Symbols 声明模块
分析转换后的代码,将其进行拆分,将非元数据部分按照一定的规则进行重新组合,形成标识符。
-
Metadata Import 元数据导入模块
将分析后的代码中数据部分进行保存,形成符号。
-
Binder 绑定模块
将符号和标识符匹配起来。
-
IL Emitter 最终产物-IL代码
通过Emitter编译为IL代码。
Compiler Api在编译过程的作用
Compiler Api在每个阶段都提供一个对象模型,让开发者可以在该阶段访问该模型来改变编译中的数据。
-
Syntax Tree API
提供一个语法树(代码大纲和格式)
-
Symbol API
分层符号表(对象浏览器和导航功能)
-
Binding and Flow Analysis APIs
语义模型(重构和定义)
-
Emit API
IL字节码的API
开发API
.NET编译器对外暴漏了多个API:编译器API,诊断API,脚本API和工作区API。
编译器API
涵盖了编译器的各个阶段,主要涉及对象模型(语法模型和语义模型),还包含最原始的程序集引用,编译器选项和源代码文件,该部分不可变。
诊断API
在编译器分析过程中,会产生一组诊断信息,包括对于语法,语义的警告或者诊断性信息。此时,用户可通过诊断API获得诊断信息,也可将用户定义的分析器插入编译过程,例如StyleCOp等。
脚本API
按照脚本语言的方式进行交互式执行代码
-
REPL(读取-评估-打印-循环)
Read(读取用户输入)-Eval(执行输入内容)-Print(打印输出结果)-Loop(循环)
工作区API
可直接访问编译器层对象模型。
Syntax Tree(语法树)
每个语法树都由节点,标记和其他项组成,用于编译,代码分析,绑定,重构,IDE功能和代码生成的主要结构。
- 完全保真的保存所有源信息。
- 生成分析源的确切文本。
- 只读且线程安全。
SyntaxTree tree = CSharpSyntaxTree.ParseText(programText);
CompilationUnitSyntax root = tree.GetCompilationUnitRoot();
语法节点
表示了声明,语句,子句和表达式等语法结构,每个类别都派生自Microsoft.CodeAnalysis.SyntaxNode来表示,不可扩展。
语法节点为语法树的非终端节点,终端为语法标记,可通过SyntaxNode.Parent访问父节点,根的父节点为null。
语法标记
语法树的终端,表示代码自小的语法片段。包含关键字,标识符,文本和标点,都采用同一结构,但是属性不同,取决于表示的标记类型。
语法其他项
包含了空格,注释和预处理的指令,Microsoft.CodeAnalysis.SyntaxTrivia类型描述琐碎的内容。
可通过SyntaxToken.LeadingTrivia 或 SyntaxToken.TrailingTrivia 集合来访问语法的其他项。 源文件中的第一个标记可获取所有初始琐碎内容,而最后一个琐碎内容序列附加到 文件尾标记,否则其宽度为零。
无父级,每个其他项都与一个标记关联,可使用SyntaxTrivia.Token属性访问关联的标记。
范围
每个节点、标记或其他项都知道其在源文本内的位置和包含的字符数。 文本位置表示为一个 32 位整数,是一个从零开始的 char 索引。 TextSpan 对象表示开始位置和字符计数,都表示为整数。
每个节点具有两个TextSpan属性:Span和FullSpan
-
Span
从节点子树中第一个标记的开头到最后一个标记末尾的文本范围。 此范围不包括任何其他项内容。
-
FullSpan
文本范围包括节点的正常范围,加上语法其他项的内容。
种类
Syntax Node.RawKing属性,标识所表示的确切语法元素。此数值可强转换为枚举类型(Microsoft.CodeAnalysis.CSharp.SyntaxKing)。
RawKind属性可轻松消除共享同一节点的语法节点类型的歧义。
错误
主要是语法错误,当分析程序遇到不符合语言定义的代码时,会创建语法树:
- 需要特定种类的标记,未找到,会在所需标记的位置插入缺失的标记,SyntaxNode.IsMissing属性返回true。
- 跳过一些标记,知道发现可继续分析的标记,跳过的内容为SkippedTokensTrivia类型的其他项目的内容节点。
语法分析
查询符号
//CSharpCompilation.AddReferences 方法将引用添加到编译。
//MetadataReference.CreateFromFile 方法加载程序集作为引用。
SyntaxTree tree = CSharpSyntaxTree.ParseText(programText);
CompilationUnitSyntax root = tree.GetCompilationUnitRoot();
var compilation = CSharpCompilation.Create("HelloWorld").AddReferences(MetadataReference.CreateFromFile(typeof(string).Assembly.Location)) .AddSyntaxTrees(tree);
查询语义模型
SemanticModel model = compilation.GetSemanticModel(tree);
绑定名称
// 使用解析树找到了"using System;"
UsingDirectiveSyntax usingSystem = root.Usings[0];
NameSyntax systemName = usingSystem.Name;
// Use the semantic model for symbol information:
SymbolInfo nameInfo = model.GetSymbolInfo(systemName);
// Microsoft.CodeAnalysis.TypeInfo结构包括 TypeInfo.Type 属性,此属性可启用对关于文本类型的语义信息的访问。
var stringTypeSymbol = (INamedTypeSymbol?)literalInfo.Type;
// 使用 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);
}
源生成器
编译用户代码时检查用户代码,动态的创建新的源文件并将这些文件添加到用户的编译中。
目前 .NET Standard 2.0 程序集只能用作源生成器
运行时反射
应用启动时对用户代码进行一定分析,并使用这些数据生成内容。
例如:ASP.NET Core在Web服务首次运行时可以使用反射来发现已定义的构造