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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章