使用 Source Generator 代替 T4 动态生成代码

使用 Source Generator 代替 T4 动态生成代码

Intro

在 Source Generator 出现之前有一些重复性的代码,我会使用 T4 去生成,这样就可以一定程度上避免复制粘贴和可维护性也会更好一些。

在了解了一些 Source Generator 之后,就想尝试把现在项目里的一些 T4 换成 Source Generator 来实现,大部分场景应该都是没有问题的,可以直接用 Source Generator 替换,而且 Source Generator 可以根据编译信息动态的去生成,更加的智能和自动化。

接着来看一下我是如何使用 Source Generator 来代替 T4 生成代码的吧

Before

首先来看一下修改之前的项目情况,项目结构是这样的

原来在 Business 项目里有一个 T4 模板,定义如下:

<#@ template  debug="false" hostSpecific="true" language="C#" #>
<#@ output extension=".generated.cs" encoding="utf-8" #>
<#@ Assembly Name="System.Core" #>
<#@ import namespace="System" #>
<#@ import namespace="System.Collections" #>
<#string[] types = {"BlockType","BlockEntity","OperationLog","Reservation","ReservationPlace","ReservationPeriod","SystemSettings","Notice","DisabledPeriod"};
#>
using OpenReservation.Database;
using OpenReservation.Models;
using WeihanLi.EntityFramework;namespace OpenReservation.Business
{
<# foreach (var item in types){
#>public partial interface IBLL<#= item #>: IEFRepository<ReservationDbContext, <#= item #>>{}public partial class BLL<#= item #> : EFRepository<ReservationDbContext, <#= item #>>,  IBLL<#= item #>{public BLL<#= item #>(ReservationDbContext dbContext) : base(dbContext){}}
<#   } 
#>
}

模板比较简单,动态生成的代码如下:

using OpenReservation.Database;
using OpenReservation.Models;
using WeihanLi.EntityFramework;namespace OpenReservation.Business
{public partial interface IBLLBlockType: IEFRepository<ReservationDbContext, BlockType>{}public partial class BLLBlockType : EFRepository<ReservationDbContext, BlockType>,  IBLLBlockType{public BLLBlockType(ReservationDbContext dbContext) : base(dbContext){}}public partial interface IBLLBlockEntity: IEFRepository<ReservationDbContext, BlockEntity>{}public partial class BLLBlockEntity : EFRepository<ReservationDbContext, BlockEntity>,  IBLLBlockEntity{public BLLBlockEntity(ReservationDbContext dbContext) : base(dbContext){}}public partial interface IBLLOperationLog: IEFRepository<ReservationDbContext, OperationLog>{}public partial class BLLOperationLog : EFRepository<ReservationDbContext, OperationLog>,  IBLLOperationLog{public BLLOperationLog(ReservationDbContext dbContext) : base(dbContext){}}public partial interface IBLLReservation: IEFRepository<ReservationDbContext, Reservation>{}public partial class BLLReservation : EFRepository<ReservationDbContext, Reservation>,  IBLLReservation{public BLLReservation(ReservationDbContext dbContext) : base(dbContext){}}public partial interface IBLLReservationPlace: IEFRepository<ReservationDbContext, ReservationPlace>{}public partial class BLLReservationPlace : EFRepository<ReservationDbContext, ReservationPlace>,  IBLLReservationPlace{public BLLReservationPlace(ReservationDbContext dbContext) : base(dbContext){}}public partial interface IBLLReservationPeriod: IEFRepository<ReservationDbContext, ReservationPeriod>{}public partial class BLLReservationPeriod : EFRepository<ReservationDbContext, ReservationPeriod>,  IBLLReservationPeriod{public BLLReservationPeriod(ReservationDbContext dbContext) : base(dbContext){}}public partial interface IBLLSystemSettings: IEFRepository<ReservationDbContext, SystemSettings>{}public partial class BLLSystemSettings : EFRepository<ReservationDbContext, SystemSettings>,  IBLLSystemSettings{public BLLSystemSettings(ReservationDbContext dbContext) : base(dbContext){}}public partial interface IBLLNotice: IEFRepository<ReservationDbContext, Notice>{}public partial class BLLNotice : EFRepository<ReservationDbContext, Notice>,  IBLLNotice{public BLLNotice(ReservationDbContext dbContext) : base(dbContext){}}public partial interface IBLLDisabledPeriod: IEFRepository<ReservationDbContext, DisabledPeriod>{}public partial class BLLDisabledPeriod : EFRepository<ReservationDbContext, DisabledPeriod>,  IBLLDisabledPeriod{public BLLDisabledPeriod(ReservationDbContext dbContext) : base(dbContext){}}
}

我是在开发时动态生成的,听大师说也可以改成在编译的时候进行生成,不过我没去尝试过,有兴趣的可以了解一下 https://docs.microsoft.com/en-us/visualstudio/modeling/run-time-text-generation-with-t4-text-templates

After

使用 Source Generator 分成了两步,第一步还是比较手动的,保留了上面的 types 数组,第二步则是自动的根据编译的信息动态的获取 types 数组

首先我们要确定哪个项目是要动态生成代码的项目,哪个项目是要写 Source Generator 的项目

原来我们用 T4 生成代码的项目(Business)就是我们要动态生成代码的项目,也就是这个项目应该是引用 Source Generator 的项目,

那我们 Source Generator 应该要放在哪个项目里呢,理论上来说要生成代码的项目哪一个都是可以的,新建一个项目也是可以的,Business 直接依赖于 Database 项目,所以我选择了 Database 项目来实现 Source Generator

Update1

首先我们需要配置 Source Generator 环境,首先为我们要写 Generator 的项目增加对 Microsoft.CodeAnalysis.CSharp 的引用

<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.9.0" />

因为 Source Generator 有外部依赖,所以需要声明依赖项,和上一篇文章类似,在项目文件中增加下面的配置:

<PropertyGroup><GetTargetPathDependsOn>;GetDependencyTargetPaths</GetTargetPathDependsOn>
</PropertyGroup>
<ItemGroup><PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.9.0" />
</ItemGroup>
<ItemGroup><PackageReference Include="WeihanLi.EntityFramework" Version="2.0.0-preview-*" GeneratePathProperty="true" />
</ItemGroup>
<Target Name="GetDependencyTargetPaths"><ItemGroup><TargetPathWithTargetPlatformMoniker Include="$(PKGWeihanLi_EntityFramework)\lib\netstandard2.1\WeihanLi.EntityFramework.dll" IncludeRuntimeDependency="false" /></ItemGroup>
</Target>

然后要动态生成代码的项目也需要配置一下,只需要修改项目文件,原来的 T4 模板可以删掉了,可以参考下面的配置

<PropertyGroup><EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
</PropertyGroup>
<ItemGroup><ProjectReference Include="..\OpenReservation.Database\OpenReservation.Database.csproj"OutputItemType="Analyzer" />
</ItemGroup>

ProjectReference 中声明 OutputItemType="Analyzer" 以使用 Generator 的功能,通过配置 EmitCompilerGeneratedFiles 以生成动态代码帮助我们调试

之后就开始写我们的 Generator 了,最终代码如下:

[Generator]
public class ServiceGenerator : ISourceGenerator
{public void Initialize(GeneratorInitializationContext context){}public void Execute(GeneratorExecutionContext context){var types = new[]{"BlockType","BlockEntity","OperationLog","Reservation","ReservationPlace","ReservationPeriod","SystemSettings","Notice","DisabledPeriod"};var codeBuilder = new StringBuilder();codeBuilder.AppendLine(@"
using OpenReservation.Database;
using OpenReservation.Models;
using WeihanLi.EntityFramework;namespace OpenReservation.Business
{");foreach (var item in types){codeBuilder.AppendLine($@"public partial interface IBLL{item}: IEFRepository<ReservationDbContext, {item}>{{}}public partial class BLL{item} : EFRepository<ReservationDbContext, {item}>,  IBLL{item}{{public BLL{item}(ReservationDbContext dbContext) : base(dbContext){{}}}}");}codeBuilder.AppendLine("}");var codeText = codeBuilder.ToString();context.AddSource(nameof(ServiceGenerator), codeText);}
}

此时,我们的 Generator 已经可以工作了,生成的代码和上面的完全一样,而且生成的代码可以不需要保存在代码库里了,编译的时候会动态生成,已经完全可以取代 T4 了

详细修改可以参考这个 Commit:https://github.com/OpenReservation/ReservationServer/commit/8a723ba652a10fb393e90bf70923631f58294da8

Update2

接着上面的修改,虽然已经代替了 T4,但是似乎并不能够体现出 Source Generator 的优势啊,于是就想再改一版,利用编译信息自动的获取上面的 types 数组,因为 types 不是随便写的是 model 的名字,所以从编译信息中获取理论上来说是可以做到的,于是有了第二版的实现,实现代码如下:

[Generator]
public class ServiceGenerator : ISourceGenerator
{public void Initialize(GeneratorInitializationContext context){// Debugger.Launch();}public void Execute(GeneratorExecutionContext context){// 从编译信息中获取 DbSet<> 类型var dbContextType = context.Compilation.GetTypeByMetadataName(typeof(DbSet<>).FullName);// 从编译信息中获取 ReservationDbContext 类型var reservationDbContextType = context.Compilation.GetTypeByMetadataName(typeof(ReservationDbContext).FullName);// 获取 ReservationDbContext 中的 DbSet<> 属性var propertySymbols = reservationDbContextType.GetMembers().OfType<IMethodSymbol>().Where(x => x.IsVirtual&& x.MethodKind == MethodKind.PropertyGet&& x.ReturnType is INamedTypeSymbol{IsGenericType: true,IsUnboundGenericType: false,} typeSymbol&& ReferenceEquals(typeSymbol.ConstructedFrom.ContainingAssembly, dbContextType.ContainingAssembly)).ToArray();// 获取属性的返回值var propertyReturnType = propertySymbols.Select(r => ((INamedTypeSymbol)r.ReturnType)).ToArray();// 获取属性泛型类型参数,并获取泛型类型参数的名称var modelTypeNames = propertyReturnType.Select(t => t.TypeArguments).SelectMany(x => x).Select(x => x.Name).ToArray();var codeBuilder = new StringBuilder();codeBuilder.AppendLine(@"
using OpenReservation.Database;
using OpenReservation.Models;
using WeihanLi.EntityFramework;namespace OpenReservation.Business
{");foreach (var item in modelTypeNames){codeBuilder.AppendLine($@"
public partial interface IBLL{item}: IEFRepository<ReservationDbContext, {item}>{{}}public partial class BLL{item} : EFRepository<ReservationDbContext, {item}>,  IBLL{item}
{{public BLL{item}(ReservationDbContext dbContext) : base(dbContext){{}}
}}");}codeBuilder.AppendLine("}");var codeText = codeBuilder.ToString();// 添加要动态生成的代码context.AddSource(nameof(ServiceGenerator), codeText);}
}

除了上面 Generator 的修改之外,还需要增加 EFCore 依赖项,这也是目前使用 SourceGenerator 的一个痛点,我的 EF 扩展 WeihanLi.EntityFramework 已经依赖了 EFCore ,但还是需要再声明一下,声明方式和前面类似

  <ItemGroup><PackageReference Include="WeihanLi.EntityFramework" Version="2.0.0-preview-*" GeneratePathProperty="true" />
+   <PackageReference Include="Microsoft.EntityFrameworkCore" Version="5.0.5" GeneratePathProperty="true" /></ItemGroup><Target Name="GetDependencyTargetPaths"><ItemGroup><TargetPathWithTargetPlatformMoniker Include="$(PKGWeihanLi_EntityFramework)\lib\netstandard2.1\WeihanLi.EntityFramework.dll" IncludeRuntimeDependency="false" />
+     <TargetPathWithTargetPlatformMoniker Include="$(PKGMicrosoft_EntityFrameworkCore)\lib\netstandard2.1\Microsoft.EntityFrameworkCore.dll" IncludeRuntimeDependency="false" /></ItemGroup></Target>

这样我们就可以通过 Source Generator 动态的自动生成 service 代码了,以后新加表只需要在 ReservationDbContext 中加入新的表就可以了,编译器也会自动生成新加表的服务类,不需要再手动配置 types 数组了,舒服~~

More

通过上面的示例,再次戳到了痛点,希望后面的版本更新中能够有所优化,也希望 VS 能够提供更有力的支持。

以上就是所有内容了,希望能够对你有所帮助,上面的示例代码可以从 https://github.com/OpenReservation/ReservationServer 进行获取

References

  • C# 强大的新特性 Source Generator

  • https://docs.microsoft.com/en-us/visualstudio/modeling/design-time-code-generation-by-using-t4-text-templates?view=vs-2019

  • https://docs.microsoft.com/en-us/visualstudio/modeling/run-time-text-generation-with-t4-text-templates?view=vs-2019

  • https://github.com/OpenReservation/ReservationServer/tree/9d2e0987d12143d297d4233bc37c06785bfa0cff/OpenReservation.Business

  • https://github.com/OpenReservation/ReservationServer/commit/8a723ba652a10fb393e90bf70923631f58294da8

  • https://github.com/OpenReservation/ReservationServer/blob/dev/OpenReservation.Database/ServiceGenerator.cs

  • https://github.com/OpenReservation/ReservationServer

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

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

相关文章

资料分享 | 数据挖掘实例资料分享来袭

小编从大学开始&#xff0c;便开启资料收集功能。随着大数据时代的来临&#xff0c;计算机发展进入新的阶段&#xff0c;再加上日常的深入研究&#xff0c;小编收集整理了丰富的数据挖掘资料&#xff0c;内容涵盖“程序”&#xff0c;“数据”、“文档”等。这次小编再次把所有…

修改图层的范围_【PS|第39期】数字绘画 使用填充图层

惟有悲观净化而成的乐观&#xff0c;才是真正的乐观。——尼采)填充图层是一种只承载纯色、渐变和图案的特殊图层&#xff0c;其特点是填充内容可以修改。另外&#xff0c;设置成不同的混合模式和不透明度后&#xff0c;可用于修改其他图层的颜色或生成图像混合效果。填充图层都…

我的注释那去了?

当我们用nuget引用三方库时&#xff0c;在类型&#xff0c;或类型成员上会有注释&#xff0c;如下图&#xff0c;是MySql官方包&#xff0c;command的ExecuteNonQuery的注释我们自己写一个类库项目CommentsLibrary&#xff0c;给类&#xff0c;构造函数&#xff0c;方法添加xml…

[原] jQuery EasyUI 1.2.6源码、Demo合集、离线API

下载地址&#xff1a; http://files.cnblogs.com/purediy/jquery-easyui-1.2.6.zip 兄弟版本&#xff1a; jQuery EasyUI 1.3.4 离线API、Demo jQuery EasyUI 1.3.2 离线API、Demo jQuery EasyUI 1.3.0 Demo合集、离线API、动态换肤 相信关注过jQuery UI 的大部分都查到过easyu…

看书的一点小建议!

阅读本文大概需要6分钟。昨天看见小北写了一篇&#xff1a;「看书的一点小建议」&#xff0c;写的很不错&#xff0c;今天也分享一下自己看书的心得。其实不少读者问过我怎么看计算机经典大厚书、怎么看产品运营经典大厚书、怎么提高看书效率&#xff1a;电影教父里有台词&…

技巧:Excel用得好,天天没烦恼

全世界有3.14 % 的人已经关注了数据与算法之美Excel是Office三大神器当中最神秘、但也是最能提高你效率的工具了。而我们中的太多小伙伴&#xff0c;却一直把它当做是个“电子表格工具”。今天一起涨姿势&#xff0c;学会下面这些神技&#xff0c;你的Excel分分钟超过90%的同事…

操作数数据类型 char 对于 sum 运算符无效。_数据类型和运算符

数据类型和运算符1.进制1.1文件存储单位​ 任何数据在计算机中都是以二进制的形式存在的&#xff0c;二进制早期由电信号开关演变而来 。​ 一个电信号或者一个二进制位统称为Bit位&#xff0c;8个Bit位为一组组成一个字节Byte 。​ 一个bit位表示的数的范围&#xff1a;0和1​…

我所理解的开源软件供应链安全

点击上方“开源社”关注我们| 作者&#xff1a;庄表伟| 编辑&#xff1a;钱英宇| 设计&#xff1a;谭嘉露| 责编&#xff1a;王玥敏1供应链与断供隐喻会帮助人&#xff0c;也会误导人。当我们谈到“供应链”时&#xff0c;会产生哪些联想&#xff1f;环环相扣&#xff1f;缺一不…

82 个代码案例实践,带你学好 Python 机器学习

全世界有3.14 % 的人已经关注了数据与算法之美如果村里通了网&#xff0c;那你一定知道【AI】人工智能。如果你会网上冲浪&#xff0c;那你一定看到过【ML】机器学习。小编在网上看到一个段子&#xff1a;ML派坐落美利坚合众山中&#xff0c;百年来武学奇才辈出&#xff0c;隐然…

查询程序崩溃日志_PC 崩溃报告途径 amp; 临时解决方法

TC Sera (社区经理)&#xff1a;你好&#xff01;如果您在电脑游戏中遇到与 Nvidia 驱动程序相关的崩溃情况&#xff0c;请打开视频设置(Video Settings)中的诊断模式(Diagnostics Mode)并重新启动游戏。如果您遇到问题&#xff0c;请打包&#xff1a;%localappdata%\Gears5\Sa…

C# ConcurrentBag的实现原理

一、前言笔者最近在做一个项目&#xff0c;项目中为了提升吞吐量&#xff0c;使用了消息队列&#xff0c;中间实现了生产消费模式&#xff0c;在生产消费者模式中需要有一个集合&#xff0c;来存储生产者所生产的物品&#xff0c;笔者使用了最常见的List<T>集合类型。由于…

Linux里10个最危险的命令

全世界只有3.14 % 的人关注了数据与算法之美Linux命令行佷有用、很高效&#xff0c;也很有趣&#xff0c;但有时候也很危险&#xff0c;尤其是在你不确定你自己在正在做什么时候。推荐阅读Linux之父林纳斯自传《只是为了好玩》这篇文章将会向你介绍十条命令&#xff0c;但你最好…

让你立刻爱上数学的10个算术游戏

全世界只有3.14 % 的人关注了数据与算法之美死理性派的小编经常会被问到的一个问题&#xff1a;数学到底哪里有趣了&#xff0c;数学之美又在哪里&#xff1f;这篇文章精心选择了 10 个老少咸宜的算术问题&#xff0c;以定理、趣题甚至未解之谜等各种形式带领大家窥探数学世界的…

iPhone 的 Push(推送通知)功能原理浅析

第一部分&#xff1a;Push原理(以下绝大多数内容参考自、图片来自iPhone OS Reference Library)机制简介Push 的工作机制可以简单的概括为下图图中&#xff0c;Provider是指某个iPhone软件的Push服务器。 APNS 是Apple Push Notification Service&#xff08;Apple Push服务器&…

编程从业五年的十四条经验,句句朴实

全世界只有3.14 % 的人关注了数据与算法之美排列不分先后&#xff1a;1. 当性能遇到问题时&#xff0c;如果能在应用层进行计算和处理&#xff0c;那就把它从数据库层拿出来。排序和分组就是典型的例子。在应用层做性能提升总是要比在数据库层容易的多。就像对于MySQL&#xff…

.Net之Swagger基础使用

介绍Swagger 是一个规范和完整的框架&#xff0c;用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。日常可以用于后端开发人员测试接口或者前后端联调使用。从.net5开始&#xff0c;swagger已经集成到vs2019编译器中&#xff0c;可以通过勾对选项“启用OpenAPI支持”显示…

孩子不是笨,他和“最强大脑”差的是这个!

小木最近看到一则消息推送&#xff0c;说家长辅导孩子陪写作业简直就是一道“送命题”。朋友圈更是掀起了一股“提前嫁儿嫁女”的热潮。为什么孩子对数学一点兴趣也没有&#xff1f;为什么再简单的一道题&#xff0c;换个数字换个形式&#xff0c;孩子就一问三不会了&#xff1…

c++ 隐藏进程_Linux 查看进程的动态信息

前言top命令经常用来监控Linux的系统状况&#xff0c;是常用的性能分析工具&#xff0c;能够实时显示系统中各个进程的资源占用情况目录一、描述二、top命令常用字段含义三、top中的子命令四、总结五、思维导图一、描述1、top命令经常用来监控Linux的系统状况是常用的性能分析工…

.net core针对async ()=的安全处理

最近在做一个功能需要传递一个委托作为回调逻辑处理&#xff0c;但在使用中定义了async ()>来处理awaiter逻辑那就存在一个安全问题了。了解async/awaiter的朋友一定清楚async void函数带来的致命风险&#xff01;async void会阻断异常路由&#xff0c;即当前函数没有try的情…

这三道题,总有一道你是答不出来的

全世界只有3.14 % 的人关注了数据与算法之美大家好&#xff0c;我是最近过得不太舒心的卢sir。经常被小思妹提的各种奇葩数学题搞得云里雾里的。我一看数学题就想做&#xff0c;没想到这些题目都是奇葩中的奇葩&#xff0c;不是那写错就是这计算错。为了捍卫我最后的倔强&#…