c# 源生成器

news2024/12/23 12:18:19

本文概述了 .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

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

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

相关文章

线程(一)

线程 1. 线程 定义&#xff1a;线程是进程的组成部分&#xff0c;不同的线程执行不同的任务&#xff0c;不同的功能模块&#xff0c;同时线程使用的资源师由进程管理&#xff0c;主要分配CPU和内存。 ​ 在进程中&#xff0c;线程执行的方式是抢占式执行操作&#xff0c;需要考…

33--Vue-前端开发-使用Vue脚手架快速搭建项目

一、vue脚手架搭建项目 node的安装: 官方下载,一路下一步 node命令类似于python npm命令类似于pip 使用npm安装第三方模块,速度慢一些,需换成淘宝镜像 以后用cmpm代替npm的使用 npm install -g cnpm --registry=https://registry.npm.taobao.org安装脚手架: cnpm inst…

汉诺塔--课后程序(Python程序开发案例教程-黑马程序员编著-第6章-课后作业)

实例3&#xff1a;汉诺塔 汉诺塔是一个可以使用递归解决的经典问题&#xff0c;它源于印度一个古老传说&#xff1a;大梵天创造世界的时候做了三根金刚石柱子&#xff0c;其中一根柱子从下往上按照从大到小的顺序摞着64片黄金圆盘&#xff0c;大梵天命令婆罗门把圆盘从下面开始…

C++回顾(二十)—— vector容器 和 deque容器

20.1 vector容器 20.1.1 vector容器简介 vector是将元素置于一个动态数组中加以管理的容器。vector可以随机存取元素&#xff08;支持索引值直接存取&#xff0c; 用[]操作符或at()方法&#xff09;。vector尾部添加或移除元素非常快速。但是在中部或头部插入元素或移除元素比…

es6的class(类)

目录 一、class&#xff08;类&#xff09;的语法 二、代码 三、效果 一、class&#xff08;类&#xff09;的语法 ES6 提供了更接近传统语言的写法&#xff0c;引入了 Class&#xff08;类&#xff09;这个概念&#xff0c;作为对象的模板。通过class关键字&#xff0c;可以…

Java基础(二):原码、反码、补码及进制之间的运算

Java基础系列文章 Java基础(一)&#xff1a;语言概述 Java基础(二)&#xff1a;原码、反码、补码及进制之间的运算 Java基础(三)&#xff1a;数据类型与进制 目录一、不同进制的表示方式二、二进制三、进制之间的转换四、byte的取值范围一、不同进制的表示方式 所有数字在计…

Leetcode 141.环形链表 142环形链表II

141环形链表 文章目录快慢指针快慢指针 代码思路&#xff1a; slow 和fast 指向 head slow走一步&#xff0c;fast走两步 没有环&#xff1a; fast每次走2步 &#xff0c;如果 fast 最终遇到NULL(链表中的元素是 偶数)或者fast->next(链表中的元素是 奇数)遇到NULL&#xf…

【ArcGIS Pro二次开发】(12):txt文件和Excel文件的读写

在Arcgis Pro的工作流中&#xff0c;数据的输入是很常见的。这里以TXT和Excel两种文件为例&#xff0c;在SDK中实现数据的读取和写入。 一、txt文件的读写 txt文件的读写相对简单&#xff0c;可以用Arcgis Pro自带的OpenItemDialog打开txt文件&#xff0c;并直接读取&#xff…

浙江大学海宁IMBA提面经验分享

先来介绍一下我的个人情况&#xff1a;本人毕业于浙江一所普通的本科院校&#xff0c;毕业已经6年了&#xff0c;在一家互联网公司担任市场部经理。其实在参加浙大IMBA项目提面之前&#xff0c;我也参加了浙大MBA项目的提面&#xff0c;可惜只拿到了良好的结果&#xff0c;所以…

力扣-每天的领导和合伙人

大家好&#xff0c;我是空空star&#xff0c;本篇带大家了解一道简单的力扣sql练习题。 文章目录前言一、题目&#xff1a;1693. 每天的领导和合伙人二、解题1.正确示范①提交SQL运行结果2.正确示范②提交SQL运行结果3.正确示范③提交SQL运行结果4.正确示范④提交SQL运行结果5.…

SWMM从入门到实践教程 04 快速入门案例的模拟执行

文章目录1 模拟时间的设置2 模拟执行3 报告查看3.1 完整报告3.2 总结报告4 纵断面查看5 结果播放1 模拟时间的设置 在左侧双击Options中的Dates&#xff0c;即可弹出时间的设置。此处为了教学&#xff0c;建议仅模拟6个小时&#xff0c;加快结果的生成。实际项目中&#xff0c;…

Gradle 的下载安装教程

Gradle 8.0.1 下载安装教程笔者的环境&#xff1a; Java 17.0.1 Gradle 8.0.1 Windows 10 教育版 64位 在继续阅读本教程之前&#xff0c;需要先完成 JDK 的安装。JDK 需要选择 8 及以上的版本。关于 JDK 的安装&#xff0c;可见笔者的另一篇博客&#xff1a; Java 的下载安…

卷积神经网络之AlexNet

目录概述AlexNet特点激活函数sigmoid激活函数ReLu激活函数数据增强层叠池化局部相应归一化DropoutAlexnet网络结构网络结构分析AlexNet各层参数及其数量模型框架形状结构关于数据集训练学习keras代码示例概述 由于受到计算机性能的影响&#xff0c;虽然LeNet在图像分类中取得了…

【轻轻松松学MongoDB】操作命令02:插入/删除

文档&#xff08;document&#xff09;的数据结构和 JSON 基本一样。所有存储在集合中的数据都是 BSON 格式。 一、文档插入 db.collection.insert( <document or array of documents>, { writeConcern: <document>, ordered: <boolean> } ) 例子&#xff…

【编程基础】009.输入两个正整数m和n,求其最大公约数和最小公倍数。

最大公约数与最小公倍数 题目描述 输入两个正整数m和n&#xff0c;求其最大公约数和最小公倍数。 输入格式 两个整数 输出格式 最大公约数&#xff0c;最小公倍数 样例输入 5 7 样例输出 1 35 题目思路 在这里我们用m表示较大的那个数&#xff0c;n表示较小的数。求…

三分钟拥有自己的 chat-gpt (开发到上线)

三分钟拥有自己的 chat-gpt (开发到上线) 首先你需要有一个 laf 账号&#xff0c;如果你还不知道 laf 是什么&#xff0c;点击这里三分钟学会然后你还需要有一个 chat-gpt 的账号并且生成一个 apiKey (这一步可以问 Google ) 云函数 具备了上面这两个条件我们就可以开始啦。…

CNCF x Alibaba云原生技术公开课 第八章 应用配置管理

Pod配置管理分类 可变配置就用 ConfigMap&#xff1b;敏感信息是用 Secret&#xff1b;身份认证是用 ServiceAccount&#xff1b;资源配置是用 Resources&#xff1b;安全管控是用 SecurityContext&#xff1b;前置校验是用 InitContainers。 1、ConfigMap 概念&#xff1a;…

Java高级技术:单元测试、反射、注解

目录 单元测试 单元测试概述 单元测试快速入门 单元测试常用注解 反射 反射概述 反射获取类对象 反射获取构造器对象 反射获取成员变量对象 反射获取方法对象 反射的作用-绕过编译阶段为集合添加数据 反射的作用-通用框架的底层原理 注解 注解概述 自定义注解 …

堆的结构与实现

堆的结构与实现二叉树的顺序结构堆的概念及结构堆的实现堆的创建向上调整建堆向下调整建堆堆的操作链接二叉树的顺序结构 堆其实是具有一定规则限制的完全二叉树。 普通的二叉树是不太适合用数组来存储的&#xff0c;因为可能会存在大量的空间浪费。而完全二叉树会更适合使用顺…

C++语法规则3(C++面向对象)

多态 C多态意味着调用成员函数时&#xff0c;会根据调用函数的对象的类型来执行不同的函数&#xff1b; 形成多态必须具备三个条件&#xff1a; 必须存在继承关系&#xff1b;继承关系必须有同名虚函数&#xff08;其中虚函数是在基类中使用关键字 virtual 声明的函数&#…