c# 源生成器
本文概述了 .NET Compiler Platform(“Roslyn”)SDK 附带的源生成器。 通过源生成器,C# 开发人员可以在编译用户代码时检查用户代码。 生成器可以动态创建新的 C# 源文件,这些文件将添加到用户的编译中。 这样,代码可以在编译期间运行。 它会检查你的程序以生成与其余代码一起编译的其他源文件。
源生成器是 C# 开发人员可以编写的一种新组件,允许执行两个主要操作:
检索表示正在编译的所有用户代码的编译对象。 可以检查此对象,并且可以编写适用于正在编译的代码的语法和语义模型的代码,就像现在使用分析器一样。
生成可在编译过程中添加到编译对象的 C# 源文件。 也就是说,在编译代码时,可以提供其他源代码作为编译的输入。
结合使用这两项操作能充分发挥源生成器的强大功能。 可以使用编译器在编译时构建的丰富元数据检查用户代码。 然后,生成器将 C# 代码发送回基于已分析数据的同一编译。 如果你熟悉 Roslyn 分析器,可以将源生成器视为可发出 C# 源代码的分析器。
源生成器作为编译阶段运行,如下所示:
源生成器是由编译器与任何分析器一起加载的 .NET Standard 2.0 程序集。 它在可以加载和运行 .NET Standard 组件的环境中使用。
常见方案
以下三种常规方法可用于检查用户代码,并基于当今技术所使用的分析生成信息或代码:
- 运行时反射。
- 处理 MSBuild 任务。
- 交织中间语言 (IL)(本文未进行讨论)。
源生成器可以是对以上每种方法的改进。
运行时反射
运行时反射是很久以前就添加到 .NET 中的一项强大技术。 使用该技术的场景不计其数。 一种常见的场景是在应用启动时对用户代码进行一定分析,并使用这些数据生成内容。
例如,ASP.NET Core 在 Web 服务首次运行时使用反射来发现你已定义的构造,使其能够“连接”控制器和 razor 页等内容。 虽然这使你能够使用强大的抽象编写简单的代码,但会在运行时影响性能:当 Web 服务或应用首次启动时,它无法接受任何请求,直到所有发现你的代码相关信息的运行时反射代码都运行完毕后才可以。 虽然这种性能影响不显著,但这是一个固定的成本,你无法在自己的应用中自我改进。
借助源生成器,启动的控制器发现阶段可以发生在编译时。 生成器可以分析源代码并发出“连接”应用所需的代码。 使用源生成器可能会加快启动时间,因为如今在运行时发生的操作可能会被推送到编译时。
处理 MSBuild 任务
源生成器也可以通过其他方式改进性能,从而发现类型,并不局限于运行时的反射。 有些场景需要多次调用 MSBuild C# 任务(称为 CSC),以便它们可以检查编译中的数据。 可以想象到的是,多次调用编译器会影响生成应用所需的总时间。 我们正在研究如何使用源生成器来避免像这样同时处理多项 MSBuild 任务,因为源生成器不仅提供了一定的性能优势,还允许工具在正确的抽象级别上运行。
源生成器可以提供的另一项功能是避免使用某些“强类型”的 API,例如 ASP.NET Core 在控制器和 razor 页面之间的路由方式。 使用源生成器时,路由可以为强类型,所需的字符串作为编译时细节生成。 这可以减少键入错误字符串文本导致请求未命中正确控制器的次数。
源生成器入门
在本指南中,你将了解如何使用 ISourceGenerator API 创建源生成器。
创建 .NET 控制台应用程序。 此示例使用 .NET 6。
将 Program 类替换为以下代码。 以下代码不使用顶级语句。 经典格式是必需的,因为第一个源生成器在该 Program 类中编写分部方法:
namespace ConsoleApp;partial partial class Program
{static void Main(string[] args){HelloFrom("Generated Code");}static partial void HelloFrom(string name);
}
接下来,我们将创建一个源生成器项目来实现 partial void HelloFrom 方法对应项。
创建一个以 netstandard2.0 目标框架名字对象 (TFM) 为目标的 .NET 标准库项目。 添加 NuGet 包 Microsoft.CodeAnalysis.Analyzers 和 Microsoft.CodeAnalysis.CSharp:
<Project Sdk="Microsoft.NET.Sdk"><PropertyGroup><TargetFramework>netstandard2.0</TargetFramework></PropertyGroup><ItemGroup><PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.4.0" PrivateAssets="all" /><PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3"><PrivateAssets>all</PrivateAssets><IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets></PackageReference></ItemGroup></Project>
创建一个名为 HelloSourceGenerator.cs 的新 C# 文件,该文件指定你自己的源生成器,如下所示:
using Microsoft.CodeAnalysis;namespace ClassLibrary1
{/// <summary>/// Hello源生成/// </summary>[Generator]public class HelloSourceGenerator : ISourceGenerator{/// <summary>/// 执行代码生成/// </summary>/// <param name="context"></param>public void Execute(GeneratorExecutionContext context){//代码生成在此处进行}/// <summary>/// 初始化/// </summary>/// <param name="context"></param>public void Initialize(GeneratorInitializationContext context){//此操作不需要初始化}}
}
源生成器需要同时实现 Microsoft.CodeAnalysis.ISourceGenerator 接口,并且具有 Microsoft.CodeAnalysis.GeneratorAttribute。 并非所有源生成器都需要初始化,本示例实现就是这种情况,其中 ISourceGenerator.Initialize 为空。
将 ISourceGenerator.Execute 方法的内容替换为以下实现:
using Microsoft.CodeAnalysis;namespace ClassLibrary1
{/// <summary>/// Hello源生成/// </summary>[Generator]public class HelloSourceGenerator : ISourceGenerator{/// <summary>/// 执行代码生成/// </summary>/// <param name="context"></param>public void Execute(GeneratorExecutionContext context){//代码生成在此处进行//查找主方法var mainMethod = context.Compilation.GetEntryPoint(context.CancellationToken);// 编译源代码string source = $@"// <auto-generated/>
using System;namespace {mainMethod.ContainingNamespace.ToDisplayString()}
{{public static partial class {mainMethod.ContainingType.Name}{{static partial void HelloFrom(string name) =>Console.WriteLine($""Generator says: Hi from '{{name}}'"");}}
}}
";var typeName = mainMethod.ContainingType.Name;//将源代码添加到编译中context.AddSource($"{typeName}.g.cs", source);}/// <summary>/// 初始化/// </summary>/// <param name="context"></param>public void Initialize(GeneratorInitializationContext context){//此操作不需要初始化}}
}
从 context 对象中,我们可以访问编译的入口点或 Main 方法。 mainMethod 实例是一个 IMethodSymbol,它表示一个方法或类似方法的符号(包括构造函数、析构函数、运算符或属性/事件访问器)。 Microsoft.CodeAnalysis.Compilation.GetEntryPoint 方法返回程序的入口点的 IMethodSymbol。 其他方法使你可以查找项目中的任何方法符号。 在此对象中,我们可以推理包含的命名空间(如果存在)和类型。 此示例中的 source 是一个内插字符串,它对要生成的源代码进行模板化,其中内插的缺口填充了包含的命名空间和类型信息。 使用提示名称将 source 添加到 context。 对于此示例,生成器创建一个新的生成的源文件,其中包含控制台应用程序中 partial 方法的实现。 可以编写源生成器来添加任何喜欢的源。
GeneratorExecutionContext.AddSource 方法中的 hintName 参数可以是任何唯一名称。 通常为该名称提供显式 C# 文件扩展名,例如 “.g.cs” 或 “.generated.cs”。 该文件名有助于将文件标识为正在生成源。
现在,我们有一个正常运行的生成器,但需要将其连接到控制台应用程序。 编辑原始的控制台应用程序项目,并添加以下内容,将项目路径替换为你在上面创建的 .NET Standard 项目中的路径:
<Project Sdk="Microsoft.NET.Sdk"><PropertyGroup><OutputType>Exe</OutputType><TargetFramework>net6.0</TargetFramework><ImplicitUsings>enable</ImplicitUsings><Nullable>enable</Nullable></PropertyGroup><!-- 将其添加为新的ItemGroup,并适当替换路径和名称 --><ItemGroup><ProjectReference Include="..\ClassLibrary1.csproj"OutputItemType="Analyzer"ReferenceOutputAssembly="false" /></ItemGroup>
</Project>
控制台项目也需要安装相应的包
新引用不是传统的项目引用,必须手动编辑以包含 OutputItemType 和 ReferenceOutputAssembly 属性。 有关 ProjectReference 的 OutputItemType 和 ReferenceOutputAssembly 属性的更多信息,请参阅常见的 MSBuild 项目项:ProjectReference。
由于正在积极改进工具体验,因此可能需要重启 Visual Studio 才能看到 IntelliSense 并消除错误。
现在,运行控制台应用程序时,应会看到生成的代码运行并打印到屏幕。 控制台应用程序本身不实现 HelloFrom 方法,而是在编译过程中从源生成器项目生成的源。 以下文本是来自此应用程序的示例输出:
如果使用的是 Visual Studio,则可以看到源生成的文件。 在“解决方案资源管理器”窗口中,展开“依赖项”>“分析器”>“SourceGenerator”>“SourceGenerator.HelloSourceGenerator”,然后双击“Program.g.cs”文件。
打开这个生成的文件时,Visual Studio 将指示该文件是自动生成的并且无法编辑。
还可以设置生成属性以保存生成的文件并控制生成的文件的存储位置。 在控制台应用程序的项目文件中,将 元素添加到 ,将其值设置为 true。 再次生成项目。 现在,生成的文件是在 obj/Debug/net6.0/generated/SourceGenerator/SourceGenerator.HelloSourceGenerator 下创建的。 路径的组成部分映射到生成器的生成配置、目标框架、源生成器项目名称和完全限定的类型名称。 可以通过将 元素添加到应用程序的项目文件来选择较方便的输出文件夹。
调试代码
重新生成就会触发调试
直接打包引用是没有效果的,修改类库的项目文件
<ItemGroup><None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false"/></ItemGroup><PropertyGroup><IncludeBuiltProjectOutputGroup>false</IncludeBuiltProjectOutputGroup></PropertyGroup>
重新打包,新建项目引用
学习地址
https://github.com/TimChen44/CC.CodeGenerator
相关文章:

c# 源生成器
本文概述了 .NET Compiler Platform(“Roslyn”)SDK 附带的源生成器。 通过源生成器,C# 开发人员可以在编译用户代码时检查用户代码。 生成器可以动态创建新的 C# 源文件,这些文件将添加到用户的编译中。 这样,代码可以…...

[N1CTF 2018]eating_cms1
一个cms,先打开环境试了一下弱口令,无效,再试一下万能密码,告诉我有waf,先不想怎么绕过,直接开扫(信息收集)访问register.php注册一个账号进行登录上面的链接尝试用php读文件http://…...

数据结构与算法基础(王卓)(15):KMP算法详解(含速成套路和详细思路剖析)
如果时间不够,急(忙)着应付考试没心思看,直接参考(照抄)如下套路: PART 1:关于next [ j ] PPT:P30 根据书上以及视频上给出的思路(提醒)&#x…...

【互联网架构】聊一聊所谓的“跨语言、跨平台“
文章目录序跨语言跨平台【饭后杂谈】为什么有人说Java的跨平台很鸡肋?序 很多技术都具有跨语言、跨平台的特点 比如JSON是跨语言的、Java是跨平台的、UniAPP、Electron是跨平台的 跨语言和跨平台,是比较重要的一个特性。这些特性经常能够决定开发者是否…...

1.JVM常识之 类加载器
1.jvm组成 JVM组成: 1.类加载器 2.运行时数据区 3.执行引擎 4.本地库接口 各组件的作用: 首先通过类加载器(ClassLoader)会把 Java 代码转换成字节码,运行时数据区(Runtime Data Area)再把字节码…...

一天搞定《AI工程师的PySide2 PyQt5实战开发手册》
PySide2/PySide6、PyQt5/PyQt6:都是基于Qt 的Python库,可以形象地这样说,PySide2 是Qt的 亲儿子(Qt官方开发的) , PyQt5 是Qt还没有亲儿子之前的收的 义子 (Riverbank Computing这个公司开发的,有商业版权限…...

身份推理桌游
目录 杀人游戏(天黑请闭眼) (1)入门版 (2)标准版 (3)延伸版——百度百科 (3.1)引入医生和秘密警察 (3.2)引入狙击手、森林老人和…...

[LeetCode周赛复盘] 第 99 场双周赛20230304
[LeetCode周赛复盘] 第 99 场双周赛20230304 一、本周周赛总结二、 [Easy] 2578. 最小和分割1. 题目描述2. 思路分析3. 代码实现三、[Medium] 2579. 统计染色格子数1. 题目描述2. 思路分析3. 代码实现四、[Medium] 2580. 统计将重叠区间合并成组的方案数1. 题目描述2. 思路分析…...

Parcel Bundle漏洞学习
Bundle的序列化细节看上去还是有些复杂的,在之前已经讨论过,一般我们使用Parcel的时候,都是严格的write和read相对应。一些疏漏,不对应,竟然就可以成为漏洞,https://xz.aliyun.com/t/2364 里介绍了Bundle漏…...

RTP载荷H264(实战细节)
RTP包由两部分组成,RTP头和RTP载荷: RTP头 RTP头的 结构如下: 代码结构: typedef struct RtpHdr {uint8_t cc : 4, // CSRC countx : 1, // header extendp : 1, // padding flagversion : 2; // versionuint8_t …...
软考高级信息系统项目管理师系列之四十三:信息系统安全管理
软考高级信息系统项目管理师系列之四十三:信息系统安全管理 一、信息系统安全管理内容二、信息安全策略1.信息系统安全策略的概念与内容2.信息系统安全等级保护的概念三、信息安全系统1.信息安全系统三维空间2.信息安全系统三种架构体系四、PKI公开密钥基础设施1.PKI总体架构2…...

并发编程之AtomicUnsafe
目录 原子操作 定义 术语 处理器如何实现原子操作 处理器自动保证基本内存操作的原子性 使用总线锁保证原子性 使用缓存锁保证原子性 Java当中如何实现原子操作 Atomic 定义 原子更新基本类型类 原子更新数组类 原子更新引用类型 原子更新字段类 Unsafe应用解析…...

GDB调试快速入门
什么是GDB: GDB - - - (GNU symbolic debugger)是Linux平台下最常用的一款程序调试器。 自己的Linux是否安装GDB? 一般来说,使用Ubuntu的话,系统就会自带的有GDB调试器的 命令窗口输入如下命令可以查看是否安装了gdb: gdb -v …...
Vim一次复制,多次粘贴
我们平常在使用Vim时候,通过viwy或者yy等复制操作之后,p操作粘贴的时候,只能粘贴一次,想要粘贴多次怎么办? 解决方案:在使用p的是时候使用"0p,这样就能无限制的一直粘贴了。 可是ÿ…...

如何修改Win11上的默认程序?
在Win10之前,更改特定文件格式的默认程序很简单,但在Win11发布之后,很多用户都不清楚关于Win11的修改默认程序的操作步骤,接下来我们就一起来看看吧,希望可以帮助到大家。 步骤如下: 一、如何更改Windows 1…...

安装Linux虚拟机和Hadoop平台教程汇总及踩坑总结
📍主要内容介绍安装Linux虚拟机、ubuntu系统、安装hadoop三个环节的教程链接介绍及本机与虚拟机的FTP传输教程总结(直接找hadoop安装环节的5.filezilla传输文件)新鲜出炉的踩坑总结和填坑指南安装Linux虚拟机和ubuntu系统一、材料和工具1、下…...
Shell脚本的使用和介绍
为了方便以后工作使用和复习,吐血整理记录一下学习shell脚本的笔记,看这篇文章需要对linux系统熟悉,希望对大家有所帮助! 文章目录 目录 文章目录 一、什么是shell? 为什么要学习和使用shell? 二、shell的分类...

机械学习 - 基础概念 - scikit-learn - 数据预处理 - 1
目录安装 scikit-learn术语理解1. 特征(feature )和样本( sample / demo)的区别?2. 关于模型的概念一、机械学习概念1. 监督学习总结:2. 非监督学习总结:3. 强化学习总结:三种学习的…...
OLCNE cluster 配置 NFS Storage(英文)
OLCNE cluster 配置 NFS Storage(英文)Create an OLCNE cluster.Create an NFS server.a. Install the NFS utility package on the server and client instances.b. Create a directory for your shared files. Make sure that the server does not hav…...

RabbitMQ高级特性
RabbitMQ高级特性 消息可靠性投递 Consumer ACK 消费端限流 TTL 死信队列 延迟队列 日志与监控 消息可靠性分析与追踪 管理 消息可靠性投递 在使用 RabbitMQ 的时候,作为消息发送方希望杜绝任何消息丢失或者投递失败场景。RabbitMQ 为我们提供了两种方式用来控制…...
后进先出(LIFO)详解
LIFO 是 Last In, First Out 的缩写,中文译为后进先出。这是一种数据结构的工作原则,类似于一摞盘子或一叠书本: 最后放进去的元素最先出来 -想象往筒状容器里放盘子: (1)你放进的最后一个盘子(…...
进程地址空间(比特课总结)
一、进程地址空间 1. 环境变量 1 )⽤户级环境变量与系统级环境变量 全局属性:环境变量具有全局属性,会被⼦进程继承。例如当bash启动⼦进程时,环 境变量会⾃动传递给⼦进程。 本地变量限制:本地变量只在当前进程(ba…...
树莓派超全系列教程文档--(62)使用rpicam-app通过网络流式传输视频
使用rpicam-app通过网络流式传输视频 使用 rpicam-app 通过网络流式传输视频UDPTCPRTSPlibavGStreamerRTPlibcamerasrc GStreamer 元素 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 使用 rpicam-app 通过网络流式传输视频 本节介绍来自 rpica…...
Java如何权衡是使用无序的数组还是有序的数组
在 Java 中,选择有序数组还是无序数组取决于具体场景的性能需求与操作特点。以下是关键权衡因素及决策指南: ⚖️ 核心权衡维度 维度有序数组无序数组查询性能二分查找 O(log n) ✅线性扫描 O(n) ❌插入/删除需移位维护顺序 O(n) ❌直接操作尾部 O(1) ✅内存开销与无序数组相…...
【ROS】Nav2源码之nav2_behavior_tree-行为树节点列表
1、行为树节点分类 在 Nav2(Navigation2)的行为树框架中,行为树节点插件按照功能分为 Action(动作节点)、Condition(条件节点)、Control(控制节点) 和 Decorator(装饰节点) 四类。 1.1 动作节点 Action 执行具体的机器人操作或任务,直接与硬件、传感器或外部系统…...
【git】把本地更改提交远程新分支feature_g
创建并切换新分支 git checkout -b feature_g 添加并提交更改 git add . git commit -m “实现图片上传功能” 推送到远程 git push -u origin feature_g...
鱼香ros docker配置镜像报错:https://registry-1.docker.io/v2/
使用鱼香ros一件安装docker时的https://registry-1.docker.io/v2/问题 一键安装指令 wget http://fishros.com/install -O fishros && . fishros出现问题:docker pull 失败 网络不同,需要使用镜像源 按照如下步骤操作 sudo vi /etc/docker/dae…...

短视频矩阵系统文案创作功能开发实践,定制化开发
在短视频行业迅猛发展的当下,企业和个人创作者为了扩大影响力、提升传播效果,纷纷采用短视频矩阵运营策略,同时管理多个平台、多个账号的内容发布。然而,频繁的文案创作需求让运营者疲于应对,如何高效产出高质量文案成…...
iOS性能调优实战:借助克魔(KeyMob)与常用工具深度洞察App瓶颈
在日常iOS开发过程中,性能问题往往是最令人头疼的一类Bug。尤其是在App上线前的压测阶段或是处理用户反馈的高发期,开发者往往需要面对卡顿、崩溃、能耗异常、日志混乱等一系列问题。这些问题表面上看似偶发,但背后往往隐藏着系统资源调度不当…...

【网络安全】开源系统getshell漏洞挖掘
审计过程: 在入口文件admin/index.php中: 用户可以通过m,c,a等参数控制加载的文件和方法,在app/system/entrance.php中存在重点代码: 当M_TYPE system并且M_MODULE include时,会设置常量PATH_OWN_FILE为PATH_APP.M_T…...