.NET初探原始碼生成(Source Generators)

HueiFeng發表於2021-03-16

前言

Source Generators顧名思義程式碼生成器,可進行建立編譯時程式碼,也就是所謂的編譯時超程式設計,這可讓一些執行時對映的程式碼改為編譯時,同樣也加快了速度,我們可避免那種昂貴的開銷,這是有價值的。

實現ISourceGenerator

整合ISourceGenerator介面,實現介面用於程式碼生成策略,它的生命週期由編譯器控制,它可以在編譯時建立時建立並且新增到編譯中的程式碼,它為我們提供了編譯時超程式設計,從而使我們對C#程式碼或者非C#原始檔進行內部的檢查。

    [Generator]
    class CustomGenerator: ISourceGenerator
    {
        public void Initialize(GeneratorInitializationContext context)
        {
            throw new NotImplementedException();
        }

        public void Execute(GeneratorExecutionContext context)
        {
            throw new NotImplementedException();
        }
    }
 public void Execute(GeneratorExecutionContext context)
{
    var compilation = context.Compilation;
    var simpleCustomInterfaceSymbol = compilation.GetTypeByMetadataName("SourceGeneratorDemo.ICustom");

    const string code = @"
using System;
namespace SourceGeneratorDemo
{   
    public partial class Program{
             public static void Display(){
             Console.WriteLine(""Hello World!"");
            }
    }
}";

            if (!(context.SyntaxReceiver is CustomSyntaxReceiver receiver))
                return;
            //語法樹可參考Roslyn Docs
            SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(code);

            //context.AddSource("a.class", code);
            context.AddSource("a.class", SourceText.From(text: code, encoding: Encoding.UTF8));

            //https://github.com/gfoidl-Tests/SourceGeneratorTest
            {
                StringBuilder sb = new();
                sb.Append(@"using System;
using System.Runtime.CompilerServices;
#nullable enable
[CompilerGenerated]
public static class ExportDumper
{
    public static void Dump()
    {");
                foreach (BaseTypeDeclarationSyntax tds in receiver.Syntaxes)
                {
                    sb.Append($@"
        Console.WriteLine(""type: {GetType(tds)}\tname: {tds.Identifier}\tfile: {Path.GetFileName(tds.SyntaxTree.FilePath)}"");");
                }
                sb.AppendLine(@"
    }
}");

                SourceText sourceText = SourceText.From(sb.ToString(), Encoding.UTF8);
                context.AddSource("DumpExports.generated", sourceText);

                static string GetType(BaseTypeDeclarationSyntax tds) => tds switch
                {
                    ClassDeclarationSyntax => "class",
                    RecordDeclarationSyntax => "record",
                    StructDeclarationSyntax => "struct",
                    _ => "-"
                };
            }
        }

context.AddSource字串形式的原始碼新增到編譯中,SourceText原文字抽象類,SourceText.From靜態方法,Code指定要建立的原始碼內容,Encoding設定儲存的編碼格式,預設為UTF8,context.SyntaxReceiver可以獲取在初始化期間註冊的ISyntaxReceiver,獲取建立的例項

實現ISyntaxReceiver

ISyntaxReceiver介面作為一個語法收集器的定義,可實現該介面,通過對該介面的實現,可過濾生成器所需

public class CustomSyntaxReceiver : ISyntaxReceiver
{
    public List<BaseTypeDeclarationSyntax> Syntaxes { get; } = new();

    /// <summary>
    ///     訪問語法樹
    /// </summary>
    /// <param name="syntaxNode"></param>
    public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
    {
        if (syntaxNode is BaseTypeDeclarationSyntax cds)
        {
            Syntaxes.Add(cds);
        }
    }
}

可以再此處進行過濾,如通過ClassDeclarationSyntax過濾Class類,當然也可以改為BaseTypeDeclarationSyntax,或者也可以使用InterfaceDeclarationSyntax新增介面類等等

除錯

對於Source Generator可以通過新增Debugger.Launch()的形式進行對編譯時的生成器進行除錯,可以通過它很便捷的一步步除錯程式碼.

 public void Initialize(GeneratorInitializationContext context)
        {
            Debugger.Launch();
            ......
        }

Library&Properties

 <ItemGroup>
    <ProjectReference Include="..\ClassLibrary1\ClassLibrary1.csproj"
                      OutputItemType="Analyzer"
                      ReferenceOutputAssembly="false" />
  </ItemGroup>
  • ReferenceOutputAssembly:如果設定為false,則不包括引用專案的輸出作為此專案的引用,但仍可確保在此專案之前生成其他專案。 預設為 true。
  • OutputItemType:可選項,將目標要輸出的型別,預設為空,如果引用值設定為true(預設值),那麼目標輸出將為編譯器的引用。

Reference

https://github.com/hueifeng/BlogSample/tree/master/src/SourceGeneratorDemo

相關文章