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

news2024/11/28 13:42:52

在阅读本文前需掌握源代码生成器相关知识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...省略");
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1216102.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux使用Docker完整安装Superset3,同时解决please use superset_config.py to override it报错

文章目录 Docker安装Superset流程1. 首先获取镜像2. 生成SSL3. 创建Superset容器4. 更新数据库5. 测试访问Superset Docker安装Superset流程 1. 首先获取镜像 docker pull amancevice/superset2. 生成SSL 接下来我们运行一些额外的程序&#xff1a; openssl rand -base64 4…

每一天的努力,都会让远方变得更近——中国人民大学与加拿大女王大学金融硕士

在这个快速变化的时代&#xff0c;我们每个人都面临着各种挑战和机遇。要想在这个世界上获得成功和成就&#xff0c;就需要不断地努力和奋斗。因为只有不断地努力&#xff0c;我们才能够实现自己的梦想和目标&#xff0c;每一天的努力都会让远方变得更近。中国人民大学与加拿大…

STM32——STM32F103时钟解析(正点原子资料+HAL库代码分析)

文章目录 前言时钟树详解系统时钟配置系统时钟使能配置 前言 上次写系统时钟解析的时候说出一篇103的时钟解析&#xff0c;我就整理HAL库开发的正点的资料&#xff0c;给小白梳理&#xff0c;我也是小白&#xff0c;不做权威使用。 时钟树详解 在 STM32 中&#xff0c;有五个…

MATLAB与Excel的数据交互

准备阶段 clear all % 添加Excel函数 try Excel=actxGetRunningServer(Excel.Application); catch Excel=actxserver(Excel.application); end % 设置Excel可见 Excel.visible=1; 插入数据 % % 激活eSheet1 % eSheet1.Activate; % 或者 % Activate(eSheet1); % % 打开…

springboot使用MongoTemplate根据正则表达式查询日期数据

一、日期正则表达式测试 匹配HH:mm:ss正则表达式写法有很多列举两个 .(点)代表任意匹配 ^开头必须是跟着的 : 精确匹配,必须是: ([0-1]?[0-9]|2[0-3]).([0-5][0-9]).([0-5][0-9]) ^([0-1]?[0-9]|2[0-3]).([0-5][0-9]).([0-5][0-9])$ ([0-1]?[0-9]|2[0-3]):([0-5][0-9]):…

【机器学习9】前馈神经网络

深度前馈网络是一类网络模型的统称&#xff0c;主要包括多层感知机、 自编码器、限制玻尔兹曼机&#xff0c; 以及卷积神经网络等。 1 激活函数 激活函数及对应导函数图其它Sigmoid 导数 在z很大或很小时都会趋近于0&#xff0c; 造成梯度消失的现象Tanh 其导数在z很大或很小…

Python接口自动化(什么是接口、接口优势、类型)

简介 经常听别人说接口测试&#xff0c;接口测试自动化&#xff0c;但是你对接口&#xff0c;有多少了解和认识&#xff0c;知道什么是接口吗&#xff1f;它是用来做什么的&#xff0c;测试时候要注意什么&#xff1f;坦白的说&#xff0c;笔者之前也不是很清楚。接下来先看一下…

模拟实现一个Linux中的简单版shell

exec系列接口中的环境变量 在之前我们学习了exec系类函数的功能就是将一个程序替换成另外一个程序。 然后就会出现下面的问题&#xff1a; 首先父进程对应的环境变量的信息是从bash中来的&#xff0c;因为我们自己写的父进程在运行的时候首先就要成为bash的子进程。这里我们将…

解决计算机丢失msvcr71.dll问题,总结5种解决方法分享

由于各种原因&#xff0c;计算机在使用的过程中可能会出现一些问题&#xff0c;其中之一就是丢失msvcr71.dll文件。这个问题可能会导致计算机无法正常运行某些程序或功能&#xff0c;给我们的生活和工作带来困扰。那么&#xff0c;当我们遇到这个问题时&#xff0c;应该如何解决…

福利来了,运营素材免费下载

各位运营的小伙伴&#xff0c;是不是在日常工作中常常用到这种场景&#xff1a;公司要做一个活动&#xff0c;老板让你写一个活动SOP&#xff0c;但是没有过往经验&#xff0c;一时无从下手&#xff0c;老板又死催。 自己想了解拉新的办法&#xff0c;但是一时找不到资料&…

双十一快递业务量暴增,快递驿站视频智能监控方案保障快递业务顺利开展

一、背景分析 虽然刚刚过去的双十一电商购物狂潮结束&#xff0c;但是快递业务量仍处在高峰期。据数据统计&#xff0c;今年全国邮政快递企业在11月11日当天共揽收快递包裹6.39亿件&#xff0c;是平日业务量的1.87倍&#xff0c;同比增长15.76%。随着电商购物节的不断增多&…

【Qt之QWizardPage】使用

介绍 QWizardPage类是向导页面的基类。 QWizard表示一个向导。每个页面都是一个QWizardPage。当创建自己的向导时&#xff0c;可以直接使用QWizardPage&#xff0c;也可以子类化它以获得更多控制。 页面具有以下属性&#xff0c;由QWizard呈现&#xff1a;a title&#xff0c;…

易点易动库存管理系统:革新企业库存管理,降本增效

在快速变化的市场环境中&#xff0c;企业面临着库存管理的巨大挑战。传统的库存管理方式耗时耗力&#xff0c;且常常因为信息滞后、数据不精确而导致库存积压或短缺。易点易动库存管理系统&#xff08;以下简称“易点易动”&#xff09;的出现&#xff0c;标志着企业库存管理进…

前端性能优化的方式

文章目录 前言DNS 预解析存储使用 HTTP / 2.0预加载预渲染懒执行与懒加载文件优化webpack优化如何根据chrome的timing优化移动端优化后言 前言 hello world欢迎来到前端的新世界 &#x1f61c;当前文章系列专栏&#xff1a;前端系列文章 &#x1f431;‍&#x1f453;博主在前端…

Linux进程之进程的状态简述

文章目录 1.百度搜索2.对进程状态的认识2.0创建状态2.1就绪状态2.2运行状态2.3阻塞状态2.4挂起状态 3.认识LinuxOS下的进程3.0进程状态的简述3.1了解R/S状态3.2D深度睡眠状态3.3信号/调试暂停状态3.4僵尸状态 1.百度搜索 2.对进程状态的认识 一个进程所具有的状态为操作系统的…

demo(三)eurekaribbonhystrix----服务降级熔断

一、介绍&#xff1a; 1、雪崩&#xff1a; 多个微服务之间调用的时候&#xff0c;假如微服务A调用微服务B和微服务C&#xff0c;微服务B和微服务C又调用其他的微服务&#xff0c;这就是所谓的"扇出"。如果扇出的链路上某个微服务的调用响应的时间过长或者不可用&am…

开发者的第一台服务器 ECS云服务器低至99元:新老同享

“阿里云始终围绕‘稳定、安全、性能、成本、弹性’的目标不断创新&#xff0c;为客户创造业务价值。”10月31日&#xff0c;杭州云栖大会上&#xff0c;阿里云弹性计算计算产品线负责人张献涛表示&#xff0c;通过持续的产品和技术创新&#xff0c;阿里云发布了HPC优化实例等多…

Kubernetes(k8s)进阶

文章目录 Kubernetes进阶一、Namespace&#xff08;名称空间&#xff09;1.namespace介绍2.管理namespace查看namespace创建namespaceyaml文件配置namespace 二、Pod&#xff08;最小基本部署单元&#xff09;1.pod介绍2.管理pod创建并运行pod查看pod信息访问pod删除podyaml文件…

如何分析伦敦金的价格走势预测?

伦敦金作为国际黄金市场的重要指标&#xff0c;其价格走势一直备受投资者关注。但是&#xff0c;黄金市场的价格变化受到多种因素的影响&#xff0c;因此要准确预测伦敦金的价格走势并非易事。在本文中&#xff0c;将介绍一些常用的方法和工具&#xff0c;帮助您分析伦敦金的价…

挖掘PostgreSQL事务的“中间态”----更加严谨的数据一致性?

1.问题 今天在上班途中&#xff0c;中心的妹纸突然找我&#xff0c;非常温柔的找我帮忙看个数据库的报错。当然以我的性格&#xff0c;妹子找我的事情对我来说优先级肯定是最高的&#xff0c;所以立马放下手中的“小事”&#xff0c;转身向妹子走去。具体是一个什么样的问题呢…