C#源代码生成器深入讲解二

20 篇文章 11 订阅

在阅读本文前需掌握源代码生成器相关知识C#源代码生成器深入讲解一

C#源代码生成器深入讲解二—增量生成器

源代码生成器有个非常大的弊病,每次都会遍历所有的语法树来分析,这样就有个问题,每次可能只修改了很少一部分或者只有很少一部分的代码需要分析,而增量源代码生成器可以理解为在之前的工作上做了一个筛选的动作,通过自定义的条件来过滤语法树,并且缓存起来,避免在没有做任何更改的情况下重复工作,提高效率。

1 增量生成器初体验

增量生成器和源代码生成器基本方法相同,只不过只需要一个Initialize方法

  1. 新建一个生成器项目
[Generator(LanguageNames.CSharp)]
public class GreetingIncrementalGenerator : IIncrementalGenerator
{
    //仅仅实现一个接口
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        //生成源代码的操作,不会立即执行,而是在编译时执行
        context.RegisterPostInitializationOutput(e =>
        {
            e.AddSource($"GreetingIncrementalGenerator.g.cs", 
                """
                //加上这句话,告知编译器,这个文件是由源代码生成器生成的,
                //防止编译器进行代码分析,避免不必要的编译器警告
                //<auto-generated>
                namespace GreetingTest;
                class GreetingIncrementalGreetingIncremental
                {
                    public static void SayHello(string name)
                    {
                        global::System.Console.WriteLine($"Hello, World {name}!");
                    }
                }
                """
                );
        });
    }
}
  1. 使用增量生成器
GreetingIncrementalGreetingIncremental.SayHello("IncrementalGeneratorInitialization");

image-20231110170341559

2 使用CreateSyntaxProvider

上一章节中直接使用字符串进行了代码生成而没有进行语法分析,语法分析可采用context.RegisterSourceOutput方法,该方法具有两个参数

  1. 声明一个分部类和分布方法
namespace SourceGenerator2
{
    public static partial class GreetingUsePartialClass
    {
        public static partial void SayHelloTo2(string name);
    }
}
  1. 新建一个类库,也就是语法生成器项目
[Generator(LanguageNames.CSharp)]
public sealed class GreetingIncrementalGenerator : IIncrementalGenerator
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        context.RegisterSourceOutput(
            //CreateSyntaxProvider接受一个判断方法,判断是否满足要求,并返回一个语法树
            context.SyntaxProvider.CreateSyntaxProvider(
                NodePredicate,
                (gsc, _) => (MethodDeclarationSyntax)gsc.Node
                ), 
            //第二个参数为上一步返回的经过判断的语法树
            (spc, method) => {

            var type = method.Ancestors().OfType<TypeDeclarationSyntax>().First();
            var typeName = type.Identifier.ValueText;
            
            spc.AddSource($"{typeName}.g.cs",

               $$"""
                //加上这句话,告知编译器,这个文件是由源代码生成器生成的,
                //防止编译器进行代码分析,避免不必要的编译器警告
                //<auto-generated>
                #nullable enable
                namespace SourceGenerator2;
                partial class {{typeName}}
                {
                    public static partial void SayHelloTo2(string name)
                    {
                        global::System.Console.WriteLine($"Hello, World {name}!");
                    }
                }
                """
                );
        });
    }
    //判断分部方法是否满足要求
    private static bool NodePredicate(SyntaxNode node, CancellationToken _)
    => node is MethodDeclarationSyntax
	{
		Identifier.ValueText: "SayHelloTo2",
		Modifiers: var methodModifiers and not [],
		ReturnType: PredefinedTypeSyntax
		{
			Keyword.RawKind: (int)SyntaxKind.VoidKeyword
		},
		TypeParameterList: null,
		ParameterList.Parameters:
		[
			{
				Type: PredefinedTypeSyntax
				{
					Keyword.RawKind: (int)SyntaxKind.StringKeyword
				}
			}
		],
		Parent: ClassDeclarationSyntax
		{
			Modifiers: var typeModifiers and not []
		}
	}
	&& methodModifiers.Any(SyntaxKind.PartialKeyword)
    && typeModifiers.Any(SyntaxKind.PartialKeyword)
    && methodModifiers.Any(SyntaxKind.StaticKeyword);
  1. 使用,在主项目中输入

GreetingUsePartialClass.SayHelloTo2("SourceGenerator2");

image-20231115143518356

3. 使用ForAttributeMetadataName

类似于C#源代码生成器深入讲解一中05章节所讲,如果想要借助特性来使用代码生成器,则可以使用ForAttributeMetadataName方法

  1. 在主项目中声明特性
namespace SourceGenerator2
{
    [AttributeUsage(AttributeTargets.Method)]
    public sealed class SayHello2Attribute:Attribute;
}
  1. 在主项目中声明一个分部方法,并标记特性
namespace SourceGenerator2
{
    public static partial class GreetingUsePartialClass
    {
        [SayHello2]
        public static partial void SayHelloToAttribute(string name);
    }
}
  1. 建立代码生成器项目
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Xml.Linq;

namespace SourceGenerator2.UseAttribute
{
    [Generator(LanguageNames.CSharp)]
    public class SourceGenerator2UseAttribute : IIncrementalGenerator
    {
        public void Initialize(IncrementalGeneratorInitializationContext context)
        {
            #region
            context.RegisterSourceOutput(
                //与上一章节中的区别是使用了ForAttributeWithMetadataName来创建Provider
                    //RegisterSourceOutput第一个参数,IncrementalValueProvider<TSource>
                    context.SyntaxProvider
                    .ForAttributeWithMetadataName("SourceGenerator2.SayHello2Attribute",
                    NodePredicate,
                    static (gasc, _) => gasc switch
                    {
                        {
                            TargetNode: MethodDeclarationSyntax node,
                            TargetSymbol: IMethodSymbol
                            {
                                Name: var methodName,
                                TypeParameters: [],
                                Parameters: [{ Type.SpecialType: SpecialType.System_String, Name: var parameterName }],
                                ReturnsVoid: true,
                                IsStatic: true,
                                ContainingType:
                                {
                                    Name: var typeName,
                                    ContainingNamespace: var @namespace,
                                    TypeKind: var typeKind and (TypeKind.Class or TypeKind.Struct or TypeKind.Interface)
                                }
                            }
                        } => new GatheredData
                        {
                            MethodName = methodName,
                            ParameterName = parameterName,
                            TypeName = typeName,
                            Namespace = @namespace,
                            TypeKind = typeKind,
                            Node = node
                        },
                        _ => null
                    }//Collect,combine等方法可对Provider进行组合
                    ).Collect(),

                    //RegisterSourceOutput第二个参数,Action<SourceProductionContext, TSource>
                    (spc, data) =>
                    {
                        foreach (var item in data)
                        {
                            if (item is null)
                            {
                                continue;
                            }
                            var namespaceName = item.Namespace.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
                            namespaceName = namespaceName["global::".Length..];
                            var typeKindString = item.TypeKind switch
                            { TypeKind.Class => "class", TypeKind.Struct => "struct", TypeKind.Interface => "interface", _ => throw new NotImplementedException() };

                            spc.AddSource(
                                $"{item.TypeName}.g.cs",
                                $$"""
                            	// <auto-generated/>
                            	#nullable enable
                            	namespace {{namespaceName}};
                            	partial {{typeKindString}} {{item.TypeName}}
                            	{
                            		{{item.Node.Modifiers}} void {{item.MethodName}}(string {{item.ParameterName}})
                            			=> global::System.Console.WriteLine($"Hello, {{{item.ParameterName}}}!");
                            	}
                            	"""
                            );
                        }
                    }

                );
            #endregion
        }
        
        public bool NodePredicate(SyntaxNode node, CancellationToken token) => node is MethodDeclarationSyntax
        {
            Modifiers: var modifiers and not [],
            Parent: TypeDeclarationSyntax { Modifiers: var typemodifiers and not [] }
        }
        && modifiers.Any(SyntaxKind.PartialKeyword)
        && typemodifiers.Any(SyntaxKind.PartialKeyword);
    }
}

file class GatheredData
{
    public string MethodName { set; get; }
    public string ParameterName { set; get; }
    public string TypeName { set; get; }
    public INamespaceSymbol Namespace { set; get; }
    public TypeKind TypeKind { set; get; }
    public MethodDeclarationSyntax Node { set; get; }
}

4. CompilationProvider和AdditionalTextsProvider

很多源代码生成器都是针对程序集的,而不是针对某个类的,所以使用CompilationProvider

[Generator(LanguageNames.CSharp)]
public class MyTupleGenerator : IIncrementalGenerator
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        //很多源代码生成器都是针对程序集的,而不是针对某个类的,所以使用CompilationProvider
        //因为CompilationProvider提供的功能有限,本次要使用TextsProvider,所以要使用Combine方法进行组合
        context.RegisterSourceOutput(context.CompilationProvider.Combine(context.AdditionalTextsProvider.Collect()), Output);
    }
    private void Output(SourceProductionContext spc, (Compilation, ImmutableArray<AdditionalText>) pair)
    {
        var (compilation, additionalFiles) = pair;
        spc.AddSource("mytuble.g.cs","source...省略");
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
动软代码生成器是一款功能强大的代码生成工具。它提供了丰富的代码模板和代码片段,可以根据用户的需求自动生成标准的代码。 首先,动软代码生成器具有友好的用户界面。它采用图形化的操作方式,使得用户可以方便地进行代码生成。通过简单的拖拽和选择操作,用户可以快速生成各种类型的代码,包括界面代码、数据访问层代码和业务逻辑层代码等。 其次,动软代码生成器内置了大量的代码模板和代码片段。用户可以根据自己的需求选择相应的模板和片段,然后将它们拖拽到代码生成区进行生成。这些模板和片段涵盖了各种常用的编程语言和框架,包括Java、C#、ASP.NET等,用户可以根据自己的项目需求进行选择和定制。 另外,动软代码生成器还支持自定义代码模板和片段。用户可以根据自己的需求创建和修改代码模板,以适应不同的项目和编码规范。通过简单的配置和设置,用户可以生成符合自己要求的代码。 最后,动软代码生成器具有可扩展性。它提供了丰富的插件和扩展机制,用户可以根据自己的需求进行扩展和定制。同时,用户还可以将生成的代码导入到其他开发工具中进行进一步的开发和调试。 综上所述,动软代码生成器是一款功能强大、易于使用且具有高度可定制性的代码生成工具。它大大提高了开发效率,减少了编码工作量,是开发人员的得力助手。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

步、步、为营

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值