日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

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

發(fā)布時(shí)間:2023/12/4 编程问答 44 豆豆
生活随笔 收集整理的這篇文章主要介紹了 使用 Source Generator 代替 T4 动态生成代码 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

使用 Source Generator 代替 T4 動(dòng)態(tài)生成代碼

Intro

在 Source Generator 出現(xiàn)之前有一些重復(fù)性的代碼,我會(huì)使用 T4 去生成,這樣就可以一定程度上避免復(fù)制粘貼和可維護(hù)性也會(huì)更好一些。

在了解了一些 Source Generator 之后,就想嘗試把現(xiàn)在項(xiàng)目里的一些 T4 換成 Source Generator 來實(shí)現(xiàn),大部分場景應(yīng)該都是沒有問題的,可以直接用 Source Generator 替換,而且 Source Generator 可以根據(jù)編譯信息動(dòng)態(tài)的去生成,更加的智能和自動(dòng)化。

接著來看一下我是如何使用 Source Generator 來代替 T4 生成代碼的吧

Before

首先來看一下修改之前的項(xiàng)目情況,項(xiàng)目結(jié)構(gòu)是這樣的

原來在 Business 項(xiàng)目里有一個(gè) 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){}} <#???}? #> }

模板比較簡單,動(dòng)態(tài)生成的代碼如下:

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){}} }

我是在開發(fā)時(shí)動(dòng)態(tài)生成的,聽大師說也可以改成在編譯的時(shí)候進(jìn)行生成,不過我沒去嘗試過,有興趣的可以了解一下 https://docs.microsoft.com/en-us/visualstudio/modeling/run-time-text-generation-with-t4-text-templates

After

使用 Source Generator 分成了兩步,第一步還是比較手動(dòng)的,保留了上面的 types 數(shù)組,第二步則是自動(dòng)的根據(jù)編譯的信息動(dòng)態(tài)的獲取 types 數(shù)組

首先我們要確定哪個(gè)項(xiàng)目是要?jiǎng)討B(tài)生成代碼的項(xiàng)目,哪個(gè)項(xiàng)目是要寫 Source Generator 的項(xiàng)目

原來我們用 T4 生成代碼的項(xiàng)目(Business)就是我們要?jiǎng)討B(tài)生成代碼的項(xiàng)目,也就是這個(gè)項(xiàng)目應(yīng)該是引用 Source Generator 的項(xiàng)目,

那我們 Source Generator 應(yīng)該要放在哪個(gè)項(xiàng)目里呢,理論上來說要生成代碼的項(xiàng)目哪一個(gè)都是可以的,新建一個(gè)項(xiàng)目也是可以的,Business 直接依賴于 Database 項(xiàng)目,所以我選擇了 Database 項(xiàng)目來實(shí)現(xiàn) Source Generator

Update1

首先我們需要配置 Source Generator 環(huán)境,首先為我們要寫 Generator 的項(xiàng)目增加對(duì) Microsoft.CodeAnalysis.CSharp 的引用

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

因?yàn)?Source Generator 有外部依賴,所以需要聲明依賴項(xiàng),和上一篇文章類似,在項(xiàng)目文件中增加下面的配置:

<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>

然后要?jiǎng)討B(tài)生成代碼的項(xiàng)目也需要配置一下,只需要修改項(xiàng)目文件,原來的 T4 模板可以刪掉了,可以參考下面的配置

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

在 ProjectReference 中聲明 OutputItemType="Analyzer" 以使用 Generator 的功能,通過配置 EmitCompilerGeneratedFiles 以生成動(dòng)態(tài)代碼幫助我們調(diào)試

之后就開始寫我們的 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);} }

此時(shí),我們的 Generator 已經(jīng)可以工作了,生成的代碼和上面的完全一樣,而且生成的代碼可以不需要保存在代碼庫里了,編譯的時(shí)候會(huì)動(dòng)態(tài)生成,已經(jīng)完全可以取代 T4 了

詳細(xì)修改可以參考這個(gè) Commit:https://github.com/OpenReservation/ReservationServer/commit/8a723ba652a10fb393e90bf70923631f58294da8

Update2

接著上面的修改,雖然已經(jīng)代替了 T4,但是似乎并不能夠體現(xiàn)出 Source Generator 的優(yōu)勢啊,于是就想再改一版,利用編譯信息自動(dòng)的獲取上面的 types 數(shù)組,因?yàn)?types 不是隨便寫的是 model 的名字,所以從編譯信息中獲取理論上來說是可以做到的,于是有了第二版的實(shí)現(xiàn),實(shí)現(xiàn)代碼如下:

[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();//?獲取屬性泛型類型參數(shù),并獲取泛型類型參數(shù)的名稱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();//?添加要?jiǎng)討B(tài)生成的代碼context.AddSource(nameof(ServiceGenerator),?codeText);} }

除了上面 Generator 的修改之外,還需要增加 EFCore 依賴項(xiàng),這也是目前使用 SourceGenerator 的一個(gè)痛點(diǎn),我的 EF 擴(kuò)展 WeihanLi.EntityFramework 已經(jīng)依賴了 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 動(dòng)態(tài)的自動(dòng)生成 service 代碼了,以后新加表只需要在 ReservationDbContext 中加入新的表就可以了,編譯器也會(huì)自動(dòng)生成新加表的服務(wù)類,不需要再手動(dòng)配置 types 數(shù)組了,舒服~~

More

通過上面的示例,再次戳到了痛點(diǎn),希望后面的版本更新中能夠有所優(yōu)化,也希望 VS 能夠提供更有力的支持。

以上就是所有內(nèi)容了,希望能夠?qū)δ阌兴鶐椭?#xff0c;上面的示例代碼可以從 https://github.com/OpenReservation/ReservationServer 進(jìn)行獲取

References

  • C# 強(qiáng)大的新特性 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

總結(jié)

以上是生活随笔為你收集整理的使用 Source Generator 代替 T4 动态生成代码的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。