元编程是什么
元编程(Meta-Programming),是一种强大的技术。与传统编程不同的是,元编程是将代码视作数据,并允许代码进行处理、修改、生成等一系列操作。
为什么需要这些?
元编程最大的好处就是可以将一些重复性高的工作(例如 DTO 声明,属性映射,MVVM)交由机器执行,从而将人解放出来专注于实现机器不能处理的业务逻辑。
一个经典的例子就是 Spring Boot,其大量利用注解和反射对代码进行修改
.NET Roslyn Source Generator
虽然 Spring Boot 面向注解编程非常高级好用,但是 Spring Boot 也有自己的缺点,因为其元编程是建立在反射上扫描注解和代码生成的。
而众所周知反射是有开销的,所以大量类扫描的开销可想而知。
既然反射有开销,那有什么办法解决呢?
当然有,既然反射有运行时开销,那么我们可以丢到编译期完成。
什么是 Source Generator?
要了解这个,我们需要先知道 C# 的编译器,也就是 Roslyn。
因为 Roslyn 设计之初采用了编译器即服务的设计理念,这也允许开发者调用 Roslyn 的 API 来对代码进行处理。
Source Generator 就是运行在 Roslyn 的一组分析程序,它将
快速上手
首先,我们需要新建一个控制台应用程序,然后在 Program.cs 写下以下内容。
namespace ConsoleApp;
public partial class Program;
使用源生成的类/方法必须标记为 partial,否则会因为重复声明而报错 ::
然后,我们需要再建一个类库项目,目标框架设置为 netstandard2.0。
使用以下命令添加必须的依赖。
dotnet add package Microsoft.CodeAnalysis.CSharp
dotnet add package Microsoft.CodeAnalysis.Analyzers # 这个是可选的,也就是装不装无所谓。
然后在 ProgramMainSourceGenerator.cs 写下以下内容。
using System.Text;
using Microsoft.CodeAnalysis;
namespace SourceGenerators;
[Generator]
public class ProgramMainSourceGenerator: IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
var sb = new StringBuilder();
sb.AppendLine("namespace ConsoleApp;");
sb.AppendLine("");
sb.AppendLine("public partial class Program{");
sb.AppendLine(" ");
sb.AppendLine(" public static void Main(string[] args) => System.Console.WriteLine(\"Hello Tsukishiro Mei!\");");
sb.AppendLine("}");
context.RegisterPostInitializationOutput(sourceGeneratorContext =>
{
sourceGeneratorContext.AddSource("Program.g.cs", sb.ToString());
});
}
}
最后在需要生成代码的 csproj 写下以下内容
<ItemGroup>
<ProjectReference Include="..\SourceGenerators\SourceGenerators.csproj" OutputItemType="Analyzer"
ReferenceOutputAssembly="false" />
</ItemGroup>
最后,我们在终端 cd 到控制台项目,运行 dotnet run。
PS D:\TsukishiroMei\Projects\RoslynExample\ConsoleApp> dotnet run
D:\TsukishiroMei\Projects\RoslynExample\SourceGenerators\ProgramMainSourceGenerator.cs(7,14): warning RS1036: “SourceGenerators.ProgramMainSourceGenerator”: 包含分析器或源生成器的项目应指定属性“<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>”
Hello Tsukishiro Mei!
是的很神奇,你的项目不仅没有编译失败,还多了一行输出。
发生了什么?
在此项目中,我们添加了一个源生成器,并让这个源生成器生成了一个静态的 Main 方法,接受 string[]。
现在,我们在引用 Source Generator 的项目 csproj PropertyGroup 添加 <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>,然后转到 obj/bin/构建配置/目标框架/generated/SourceGenerators/SourceGenerators.ProgramMainSourceGenerator 下查看生成的 Program.g.cs
你就会看到 Source Generator 为你生成了一个 partial class Program,里面包含了一个 Main 方法。
namespace ConsoleApp;
public partial class Program{
public static void Main(string[] args) => System.Console.WriteLine("Hello Tsukishiro Mei!");
}
回看我们之前写的 Source Generator,你会发现 Source Generator 输出的正是我们在 StringBuilder 写的东西。
这里我们用的是比较基础的 RegisterPostInitializationOutput,它假定生成的代码和你写的代码没有关系,所以也就不需要碰一些相对复杂的任务。
小结
本章我们了解了源生成器的基本概念,现在你可以去写源生成器了
如果你用的是 Rider,也可以在依赖项 -> 源生成器上看到 Source Generator 上看生成输出,不过我写这篇文章的时候用的是 VSCode。
気に入ったならばコメントを残してくださいね~