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

  • A+
所属分类:.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="..ClassLibrary1ClassLibrary1.csproj"                       OutputItemType="Analyzer"                       ReferenceOutputAssembly="false" />   </ItemGroup> 
  • ReferenceOutputAssembly:如果设置为false,则不包括引用项目的输出作为此项目的引用,但仍可确保在此项目之前生成其他项目。 默认为 true。
  • OutputItemType:可选项,将目标要输出的类型,默认为空,如果引用值设置为true(默认值),那么目标输出将为编译器的引用。

Reference

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