Source Generator:C# 9将迎来编译时元编程

源码生成器(Source Generator)是C#编译器的一个新特性,开发者可以使用编译器生成的元数据检查用户代码,并生成附加的源文件,与程序的其他部分一起编译。

F#类型提供程序的启发,C#源码生成器的目标也是为了启用元编程,只是以一种完全不同的方式。实际上,F#类型提供程序在内存中触发类型、属性和方法,而源码生成器是将C#代码重新加入编译过程。

源码生成器不能修改已有代码,只能向编译添加新代码。源码生成器的另一个限制是它不对其他源码生成器生成的代码起作用。这样可以确保每个代码生成器将看到相同的编译输入,而不管应用程序的顺序是怎样的。有趣的是,源码生成器并不局限于检查源代码及其相关的元数据,它们还可以访问其他文件。

具体来说,源码生成器并不是代码重写工具,比如优化器或代码注入器,也不是用来创建新的语言特性的,尽管这在技术上来说是可行的。源码生成器的使用场景包括自动接口实现、数据序列化,等等。在源码生成器指南中可以找到更多应用场景,其中还包含了讨论内容。

源码生成器与Roslyn代码分析器有很大的关系,这从它的接口定义可以很明显地看出来:

namespace Microsoft.CodeAnalysis
{
    public interface ISourceGenerator
    {
        void Initialize(InitializationContext context);
        void Execute(SourceGeneratorContext context);
    }
}

编译器调用Initialize方法,生成器注册一些稍后将会调用的回调函数。代码生成发生在Execute方法里,它的参数是一个SourceGeneratorContext对象,该对象提供对当前Compilation对象的访问。

namespace Microsoft.CodeAnalysis
{
    public readonly struct SourceGeneratorContext
    {
        public ImmutableArray<AdditionalText> AdditionalFiles { get; }

        public CancellationToken CancellationToken { get; }

        public Compilation Compilation { get; }

        public ISyntaxReceiver? SyntaxReceiver { get; }

        public void ReportDiagnostic(Diagnostic diagnostic) { throw new NotImplementedException(); }

        public void AddSource(string fileNameHint, SourceText sourceText) { throw new NotImplementedException(); }
    }
}

可以修改SourceGeneratorContext对象,使用AddSource来包含其他代码。正如上面提到的,源码生成器不仅限于C#文件。这从AdditionalFiles就可以看出来,它支持传给编译器的任意文件。

综上所述,要为“hello world”程序定义一个普通的源码生成器可以这样:

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;

namespace SourceGeneratorSamples
{
    [Generator]
    public class HelloWorldGenerator : ISourceGenerator
    {
        public void Execute(SourceGeneratorContext context)
        {
            // begin creating the source we'll inject into the users compilation
            var sourceBuilder = new StringBuilder(@"
using System;
namespace HelloWorldGenerated
{
    public static class HelloWorld
    {
        public static void SayHello() 
        {
            Console.WriteLine(""Hello from generated code!"");
            Console.WriteLine(""The following syntax trees existed in the compilation that created this program:"");
");

            // using the context, get a list of syntax trees in the users compilation
            var syntaxTrees = context.Compilation.SyntaxTrees;

            // add the filepath of each tree to the class we're building
            foreach (SyntaxTree tree in syntaxTrees)
            {
                sourceBuilder.AppendLine($@"Console.WriteLine(@"" - {tree.FilePath}"");");
            }

            // finish creating the source to inject
            sourceBuilder.Append(@"
        }
    }
}");

            // inject the created source into the users compilation
            context.AddSource("helloWorldGenerator", SourceText.From(sourceBuilder.ToString(), Encoding.UTF8));
        }

        public void Initialize(InitializationContext context)
        {
            // No initialization required for this one
        }
    }
}

微软已经发布了更多的介绍性示例,向开发人员展示如何使用这个新特性。

源代码生成器可在.NET 5预览版和最新的Visual Studio预览版中使用。这个特性仍然处于早期阶段,它的API和特性可能会在将来的版本中发生变化。

原文链接

Source Generators Will Enable Compile-Time Metaprogramming in C# 9

  • 发表于:
  • 本文为 InfoQ 中文站特供稿件
  • 首发地址https://www.infoq.cn/article/760JGQlMNbKlWaukylG7
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

扫码关注云+社区

领取腾讯云代金券