天天看點

.NET初探源代碼生成(Source Generators)

前言

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