抽丝剥茧!Source Generators原理讲解

前言

前段时间,我们已经用Source Generators实现了好多功能,比如AutoMapper、API最佳实践。

你看完那些实现代码,是不是还有点云里雾里!

Source Generators到底是怎么做到的?

基础知识

Source Generators是编译过程的一部分,它以编译树作为输入,通过分析代码,动态生成文件并把它们加入到编译过程中:

需要注意的是,你只能添加一些东西到代码,但不能改变现有的代码。

为了使用Source Generators,你必须创建.Net Standard项目,并引用nuget包Microsoft.CodeAnalysis.CSharp 3.8.0或以上版本。

基本的实现代码如下,你必须实现ISourceGenerator接口,并且用GeneratorAttribute标注:

[Generator]
public class DemoSourceGenerator : ISourceGenerator
{
    public void Execute(GeneratorExecutionContext context)
    {
        throw new NotImplementedException();
    }

    public void Initialize(GeneratorInitializationContext context)
    {
        throw new NotImplementedException();
    }
}

生成器执行上下文

主要生成过程通过Execute方法执行。

Execute传递一个GeneratorExecutionContext实例,下列是实例常用的属性和方法:

  • AdditionalFiles 获取当前编译项目文件中的所有AdditionalFiles标签

  • Compilation 编译上下文,最重要的对象

  • AddSource 向编译器加入代码,最重要的方法

语法树

通过GeneratorExecutionContext.Compilation我们可以获得编译上下文,有了这个对象,你就可以访问当前编译项目的整个语法树(SyntaxTree)。

那什么是语法树呢?

首先,安装.NET Compiler Platform SDK

然后,在VS中打开“视图”->“其他窗口”->“Syntax Visualizer”。

可以看到,语法树是一个树形结构,和每一行代码一一对应

语法树包含三种类型的项——node、token和trivia。

比如public class Class1 { }整体是ClassDeclaration node,下级的Class1则是ClassKeyword token, 而紧跟的空格则是Whitespace trivia

因此,只要我们遍历语法树,即可拿到编译中的任何代码。

Demo

现在把上面的综合起来,我们就可以开发Source Generators功能了:

public void Execute(GeneratorExecutionContext context)
{
    //获取第一个附加文件内容,用作代码模板
    var template = context.AdditionalFiles.First().GetText().ToString();

    //获取第一个类名
    var className = context.Compilation.SyntaxTrees.SelectMany(p => p.GetRoot().DescendantNodes().OfType<ClassDeclarationSyntax>()).First().Identifier.Text;
    
    // 替换文本生成代码
    // 你也可以使用模板引擎或者StringBuilder拼接出代码
    var source = template.Replace("{Class}", className);
    
    // 向编译过程添加代码文件
    context.AddSource("Demo", SourceText.From(source, Encoding.UTF8));
}

在待编译的项目中添加一个附加文件

<ItemGroup>
    <AdditionalFiles Include="template.txt"  />
</ItemGroup>

template.txt的文件内容如下:

using System;

namespace ClassLibrary1
{
    public static class Demo
    {
        public static void SayHello()
        {
            Console.WriteLine("Hello {Class}!");
        }
    }
}

编译后,可以看到生成如下代码:

结论

希望我已经描述清楚了使用Source Generators的整个过程。

期待你用它开发出更多更好的功能!

如果你觉得这篇文章对你有所启发,请关注我的个人公众号”My IO“,记住我!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值