维基百科对编译器的解释是:编译器是一种程序,它将某种编程语言编写的源代码(原始语言)转换成另一种编程语言(目标语言)。编译是从源代码(通常为高阶语言)到能直接被计算机或虚拟机执行的目标代码(通常为低阶语言或机器语言)的翻译过程。
在 .NET 平台中,在执行模型的不同阶段有两个不同的编译器:一个叫 Roslyn 编译器,负责把 C# 和 VB 代码编译为程序集;另一个叫 RyuJIT 编译器,负责把程序集中的 IL(中间语言) 代码编译为机器码。
本文先介绍 Roslyn 编译器。我们不必深入研究它的工作原理,但要了解它的工作机制,要知道它可以用来做什么事情。
最初 C# 语言的编译器是用 C++ 编写的,后来微软推出了一个新的用 C# 自身编写的编译器:Roslyn,它属于自举编译器。
所谓自举编译器就是指,某种编程语言的编译器就是用该语言自身来编写的。自举编译器的每个版本都是用该版本之前的版本来编译的,但它的第一个版本必须由其它语言编写的编译器来编译,比如 Roslyn 的第一个版本是由 C++ 编写的编译器来编译的。很多编程语言发展成熟后都会用该语言本身来编写自己的编译器,比如 C# 和 Go 语言。
在 .NET 平台,Roslyn 编译器负责将 C# 和 VB 代码编译为程序集。
大多数现有的传统编译器都是“黑盒”模式,它们将源代码转换成可执行文件或库文件,中间发生了什么我们无法知道。与之不同的是,Roslyn 允许你通过 API 访问代码编译过程中的每个阶段。
它的工作机制是管道式的,整个工作管道包含四个阶段,每个阶段都是一个独立的模块,每个模块都提供了相应的 API。集成开发环境(IDE)可以利用这些 API 提供方便的工具以提高开发效率,如代码高亮、智能提示、重构工具、性能分析工具等。此外,通过 Roslyn,开发者可以在自己的程序中使用编译器,将编译器作为一种服务来使用。
下图描绘了 Roslyn 工作管道的各个阶段和各阶段对应的 API,以及各 API 可为 IDE 提供的对应功能:
Parser(解析)阶段,根据语言语法对源代码进行解析,将源代码转换为层次化的标记集合,形成语法树。语法树 API 用于在源代码编辑器中格式化、着色和代码大纲。
Declaration(声明)阶段,分析所有引用和导入的元数据,形成层次化的符号表。在编辑器和对象浏览器中的
Navigation To
特性使用这个 API。Bind(绑定)阶段,对标记集合和符号表进行匹配。编辑器中的
Find All References
、Rename
、Quick Info
和Extract Method
等特性使用这个 API。Emit(生成)阶段,生成 IL 托管模块,将一个或多个 IL 托管模块和嵌入资源合并成程序集。编辑器中的
Edit and Continue
利用这个特性完成一次新的编译。
Roslyn 是少数几个让你有机会观察所有编译阶段和中间结果的编译器之一,它提供的这些 API 可以为语言服务实现丰富的功能。例如,代码高亮使用语法树,对象浏览器使用分层符号表。
下面我们来做一个简单的示例,利用 Roslyn 提供的 API 来动态生成代码。
创建一个控制台应用程序 ConsoleApp
,编辑 Program.cs
的代码如下:
using System;namespace ConsoleApp
{partial class Program{static void Main(string[] args){HelloFrom("Generated Code");Console.ReadKey();}static partial void HelloFrom(string name);}
}
再创建一个 .NET Standard 类库,取名 MyGenerator
,并添加两个 NuGet 包,项目文件内容如下:
<Project Sdk="Microsoft.NET.Sdk"><PropertyGroup><TargetFramework>netstandard2.0</TargetFramework></PropertyGroup><ItemGroup><PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.2" /><PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.10.0" /></ItemGroup></Project>
然后在 MyGenerator
项目中添加一个 Generator.cs
文件,代码如下:
using Microsoft.CodeAnalysis;namespace MyGenerator
{[Generator]public class Generator : ISourceGenerator{public void Initialize(GeneratorInitializationContext context){}public void Execute(GeneratorExecutionContext context){// find the main methodvar mainMethod = context.Compilation.GetEntryPoint(context.CancellationToken);// build up the source codestring source = $@"
using System;namespace {mainMethod.ContainingNamespace.Name}
{{public static partial class {mainMethod.ContainingType.Name}{{static partial void HelloFrom(string name){{Console.WriteLine($""Generator says: Hi from '{{name}}'"");}}}}
}}
";// add the source code to the compilationcontext.AddSource("generatedSource", source);}}
}
这里的 source
是我们的动态组装的代码,在实际应用中还可以从数据库或文本中读取代码片段。
最后在 ConsoleApp
项目中引用 MyGenerator
类库,并参照如下代码设置 OutputItemType
和 ReferenceOutputAssembly
属性:
<ItemGroup><ProjectReference Include="..\MyGenerator\MyGenerator.csproj"OutputItemType="Analyzer"ReferenceOutputAssembly="false" /></ItemGroup>
运行 ConsoleApp
,可以看到控制台输出如下内容:
Roslyn 的功能非常强大,这个示例只是演示了 Roslyn 的一个非常简单的功能和用途。
Roslyn 不只是一个编译器,还是一个现成的框架,它使得在 .NET 平台上创建自己的语言服务变得更加容易。你可以使用 Roslyn 编译器的 API 在 .NET 平台上开发一个完整的应用程序,甚至创建你自己的 IDE、编写你自己的编译器、解释器或分析器来编译和运行你自己的编程语言。
加入我们,一起踏上.NET大牛成长之路↓