.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

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