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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

异行星平台--CRM系统搭建

使用异行行低代码平台搭建的CRM系统。CRM系统中&#xff0c;包括“客户”、“商机”、“合同”、“回款”、“开票”、“营销”、“回访”、“产品”、“联络人”、“销售订单”、“线索”。 “管理后台”可能是指一个用于管理后台系统的应用&#xff0c;可能包括对各种功能和数…

基于django电影推荐系统

基于django电影推荐系统 摘要 该Django电影推荐系统是一个简单而基础的框架&#xff0c;旨在展示系统的基本组件。系统包括两个主要模型&#xff0c;即Movie和Rating&#xff0c;用于存储电影信息和用户评分。视图层包括展示电影列表和电影详情的功能&#xff0c;使用模板进行页…

从底层认识哈希表【C++】

目录 一. unordered系列关联式容器 二. unordered_map的文档介绍 接口使用 三. 底层实现 &#xff08;1&#xff09;哈希概念 例&#xff1a; &#xff08;2&#xff09;哈希冲突 &#xff08;3&#xff09;冲突解决 1.闭散列​​​​​​​ 闭散列框架 插入 查找 删除 2.开散…

虾皮店铺所有商品数据接口(shopee.item_search_shop)

虾皮店铺所有商品数据接口可以提供丰富的电商数据&#xff0c;包括商品数据、订单数据、会员数据、评价数据等。以下是具体的介绍&#xff1a; 商品数据&#xff1a;虾皮提供了商品的基本信息&#xff0c;包括商品名称、描述、规格、价格、销量、库存等信息。此外&#xff0c;…

【网络知识必知必会】再谈Cookie和Session

文章目录 前言1. 回顾 Cookie1.1 到底什么是 CookieCookie 的数据从哪里来Cookie 的数据长什么样Cookie 有什么作用Cookie 到哪里去Cookie 怎么存储的 2. 理解 Session3. Cookie 和 Session 的区别总结 前言 在讲 HTTP 协议时, 我们就谈到了 Cookie 和 Session, 当时我们只是粗…

【部署篇】Docker配置MySQL容器+远程连接

一、前言 上篇文章在部署nestjs时&#xff0c;由于docker访问不了主机的localhost&#xff0c;所以无法连接主机数据库。所以我们只能在docker中额外配置一个数据库&#xff0c;映射到主机上&#xff0c;然后可以通过ip地址访问。 在本篇文章我们会在docker中创建一个mysql&a…

Logback 日志格式参数说明

打印日志格式&#xff1a; <property name"LOG_PATTERN" value"[${APP_NAME} ${SERVER_IP}:${SERVER_PORT}] %d{yyyy-MM-dd HH:mm:ss.SSS} %level ${PID} [%X{traceId}-%X{spanId}] [%thread] %logger %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}"/&g…

这7个“小毛病”项目经理必须克服

大家好&#xff0c;我是老原。 项目经理干项目可能不在行&#xff0c;但“踩坑”、“背锅”一定在行。 当上项目经理不容易&#xff0c;当好项目经理更不容易&#xff0c;有永远填不完的坑和背不完的锅。 如果要问项目经理都踩过哪些坑&#xff0c;那真的是太多了&#xff0…

【邻接表,图的邻接表存储表示】

文章目录 邻接表无向图有向图图的邻接表存储表示&#xff1a;图的邻接表的弧&#xff08;边&#xff09;的结点结构 邻接矩阵的好处&#xff1a; 1.直观&#xff0c;简单&#xff0c;好理解。 2.方便检查任意一对顶点间是否存在边 3.方便找到任一顶点的所有“邻接点”&#xff…

思考如何完成一个审批流

思考如何完成一个审批流 这篇文章&#xff0c;可能没有太多的干货&#xff0c;只是对于自己做过项目的一个反思与整理&#xff0c;同时&#xff0c;让这篇文章暴露在公共视野&#xff0c;虚心接受批评指导&#xff0c;向各位前辈同仁进行学习。 如果此文又不当之处&#xff0c;…

MIB 操作系统Lab: Xv6 and Unix utilities(1)boot xv6

从github中下载xv6代码 $ git clone git://g.csail.mit.edu/xv6-labs-2023 $ cd xv6-labs-2023 编译和运行xv6: $ make qemu 如果在终端输入ls命令&#xff0c;能看到输出。 大多数都是可以直接运行的命令。 xv6没有ps命令&#xff0c;但是可以输入ctrl-p可以看到进程的信…

【C语言】动态内存管理

简单不先于复杂&#xff0c;而是在复杂之后 文章目录 1. 为什么存在动态内存分配2. 动态内存函数的介绍2.1 [malloc ](http://www.cplusplus.com/reference/cstdlib/malloc/?kwmalloc)和 [free](https://cplusplus.com/reference/cstdlib/free/)2.2 [calloc](https://cplusplu…

历年国自然标书申请 面上项目614份 2001-2019年 面上标书

这里列举几例 清华任丰原 哈尔滨 杨宝峰 # 关注微信&#xff1a;生信小博士&#xff0c;10元领取 关注微信之后&#xff0c; 点开付费合集即可领取

C51--WiFi模块ESP8266--AT指令

ESP8266 面向物联网应用的&#xff0c;高性价比、高度集成的WiFi MCU 简介&#xff1a; 高度集成&#xff1a; ESP8266EX集成了32位Tensilica 处理器、标准数字外设接口、天线开关、射频balun、功率放大器、底噪放大器、过滤器和电源管理模块&#xff0c;可将所占的PCB空间降…

Jetson orin nano配置深度学习环境

Jetson orin nano是一块比较新的板子&#xff0c;装的是Ubuntu20.04系统&#xff0c;与普通x86_64不同&#xff0c;它是ARM64平台&#xff0c;网上的教程不是很多。 一、Jeston Orin Nano介绍 2022年GTC&#xff0c;NVIDIA 宣布Jetson Orin Nano系列系统模块&#xff08;SoM&a…

【教3妹学编程-算法题】最大化数组末位元素的最少操作次数

3妹&#xff1a;“太阳当空照&#xff0c;花儿对我笑&#xff0c;小鸟说早早早&#xff0c;你为什么背上炸药包” 2哥 :3妹&#xff0c;什么事呀这么开发。 3妹&#xff1a;2哥你看今天的天气多好啊&#xff0c;阳光明媚、万里无云、秋高气爽&#xff0c;适合秋游。 2哥&#x…

SpringMVC调用流程

SpringMVC的调用流程 SpringMVC涉及组件理解&#xff1a; DispatcherServlet : SpringMVC提供&#xff0c;我们需要使用web.xml配置使其生效&#xff0c;它是整个流程处理的核心&#xff0c;所有请求都经过它的处理和分发&#xff01;[ CEO ] HandlerMapping : SpringMVC提供&…

yolo改进替换VanillaNet backbone

论文地址&#xff1a;https://arxiv.org/pdf/2305.12972.pdf 代码地址&#xff1a;GitHub - huawei-noah/VanillaNet VanillaNet简介 基础模型的核心是“更多不同”的哲学&#xff0c;计算机视觉和自然语言处理的惊人成功就是例证。 然而&#xff0c;优化的挑战和Transformer模…

pycharm2023关闭项目后一直显示正在关闭项目-解决办法

网上的很多教程都试了不行&#xff0c;直接用下面的方法有效解决。 点击 帮助--查找操作--输入Registry--点注册表&#xff0c;取消ide.await.scope.completion后的勾选即可。

rabbitMQ的扇出模式(fanout发布订阅)的生产者与消费者使用案例

扇出模式 fanout 发布订阅模式 生产者 生产者发送消息到交换机&#xff08;logs&#xff09;,控制台输入消息作为生产者的消息发送 package com.esint.rabbitmq.work03;import com.esint.rabbitmq.RabbitMQUtils; import com.rabbitmq.client.Channel;import java.util.Scanne…