前言

前段时间,我们已经用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“,记住我!

My IO
My IO
【每日推送】将输入变成输出,每天成长一小步
128篇原创内容
公众号
来源:https://mp.weixin.qq.com/s/Sz_z56qb3ewfuZAYNMzqRQ
点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信小程序

微信扫一扫体验

立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部