SourceGenerator入门指北

1 SourceGenerator介绍#

SourceGenerator于2020年4月29日在微软的.net blog首次介绍,大概说的是开发者编可以写分析器,在项目代码编译时,分析器分析项目既有的静态代码,允许添加源代码到GeneratorExecutionContext中,一同与既有的代码参与编译。

2 SourceGenerator未出生时#

在还没有SourceGenerator的时候,开发者要实现AOP框架时,往往使用以下技术:

  • Emit技术,运行时生成代理类型,难点比较低且不用考虑语言的语法,但不适用于需要完全AOT编译的平台。
  • msbulid+代码分析+代码生成,拦截build的某个阶段运行task,task分析既有代码的语法,然后生成代理代码到编译器中。
  • msbuild+Mono.Cecil, 拦截build的某个阶段运行task,task通过Cecil静态修改编译输出的程序集,补充代理IL到程序集中,然后程序集可能会继续参与下一步的AOT编译过程。

WebApiClient.JIT与WebApiClient.AOT包,分别适用上面的Emit和Cecil,后者难度非常大,且表现得不太稳定。

3 第一个吃螃蟹的落地项目#

一直比较关心SourceGenerator,现在我觉得,SourceGenerator现在已到达可以使用的阶段了。WebApiClientCore之前有个分支做SourceGenerator的实验,但迟迟没有合并到master来。现在它已经合并到master,并以一个Extensions.SourceGenerator扩展包的方式出现,让WebApiClientCore多一种代理类生成的方式选择。这个扩展包编写时非常简单,我已经不想看以前是怎么用Cecil为程序集插入静态IL的代码了。

4 如何编写xxxSourceGenerator#

创建一个netstandard2.0的程序集#

Copy
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>netstandard2.0</TargetFramework> <LangVersion>8.0</LangVersion> <Nullable>enable</Nullable> </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.8.0" PrivateAssets="all" /> <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.2" PrivateAssets="all" /> </ItemGroup> </Project>

实现ISyntaxReceiver,接收编译时语法树的遍历#

Copy
class xxxSyntaxReceiver : ISyntaxReceiver { /// <summary> /// xxx感兴趣的接口列表 /// </summary> private readonly List<InterfaceDeclarationSyntax> interfaceSyntaxList = new List<InterfaceDeclarationSyntax>(); /// <summary> /// 访问语法树 /// </summary> /// <param name="syntaxNode"></param> void ISyntaxReceiver.OnVisitSyntaxNode(SyntaxNode syntaxNode) { if (syntaxNode is InterfaceDeclarationSyntax syntax) { this.interfaceSyntaxList.Add(syntax); } } }

实现ISourceGenerator,且使用[Generator]特性#

Copy
[Generator] public class xxxSourceGenerator : ISourceGenerator { /// <summary> /// 初始化 /// </summary> /// <param name="context"></param> public void Initialize(GeneratorInitializationContext context) { context.RegisterForSyntaxNotifications(() => new xxxSyntaxReceiver()); } /// <summary> /// 执行 /// </summary> /// <param name="context"></param> public void Execute(GeneratorExecutionContext context) { if (context.SyntaxReceiver is xxxSyntaxReceiver receiver) { // 从receiver获取你感兴趣的语法节点 // 然后拼接成string的代码 // 把代码添加到context context.AddSource("代码1的id""这里是c#代码,会参与编译的"); } } }

5 如何调试xxxSourceGenerator#

在被调试项目以分析器方式引入xxxSourceGenerator项目#

Copy
<ItemGroup> <ProjectReference Include="..\xxxSourceGenerator\xxxSourceGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" /> </ItemGroup>

在xxxSourceGenerator里加入Debugger.Launch()#

没错,这是最简单的触发调试方式,你在xxxSourceGenerator入口加这么一行代码,被调试的项目只要一编译,vs就弹出且断点到Debugger.Launch()这行,然后就可以一步一步执行调试了。

6 如何打包发布xxxSourceGenerator#

SourceGenerator项目本质上还是分析器项目,所以可以打包成一个nuget包,别的项目引用这个nuget包之后,就自动以分析器的方式安装到目标项目中,然后激活了你的xxxSourceGenerator。

分析器的nuget打包#

  • 需要将编译出的xxxSourceGenerator.dll放到nuget包的analyzers\dotnet\cs目录下
  • 需要在nuget包的tools目录下放置分析器安装和卸载脚本install.ps1和uninstall.ps1,这脚本是通用的。

install.ps1#

Copy
param($installPath, $toolsPath, $package, $project) $analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers" ) * -Resolve foreach($analyzersPath in $analyzersPaths) { # Install the language agnostic analyzers. if (Test-Path $analyzersPath) { foreach ($analyzerFilePath in Get-ChildItem $analyzersPath -Filter *.dll) { if($project.Object.AnalyzerReferences) { $project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName) } } } } # $project.Type gives the language name like (C# or VB.NET) $languageFolder = "" if($project.Type -eq "C#") { $languageFolder = "cs" } if($project.Type -eq "VB.NET") { $languageFolder = "vb" } if($languageFolder -eq "") { return } foreach($analyzersPath in $analyzersPaths) { # Install language specific analyzers. $languageAnalyzersPath = join-path $analyzersPath $languageFolder if (Test-Path $languageAnalyzersPath) { foreach ($analyzerFilePath in Get-ChildItem $languageAnalyzersPath -Filter *.dll) { if($project.Object.AnalyzerReferences) { $project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName) } } } }

uninstall.ps1#

Copy
param($installPath, $toolsPath, $package, $project) $analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers" ) * -Resolve foreach($analyzersPath in $analyzersPaths) { # Uninstall the language agnostic analyzers. if (Test-Path $analyzersPath) { foreach ($analyzerFilePath in Get-ChildItem $analyzersPath -Filter *.dll) { if($project.Object.AnalyzerReferences) { $project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName) } } } } # $project.Type gives the language name like (C# or VB.NET) $languageFolder = "" if($project.Type -eq "C#") { $languageFolder = "cs" } if($project.Type -eq "VB.NET") { $languageFolder = "vb" } if($languageFolder -eq "") { return } foreach($analyzersPath in $analyzersPaths) { # Uninstall language specific analyzers. $languageAnalyzersPath = join-path $analyzersPath $languageFolder if (Test-Path $languageAnalyzersPath) { foreach ($analyzerFilePath in Get-ChildItem $languageAnalyzersPath -Filter *.dll) { if($project.Object.AnalyzerReferences) { try { $project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName) } catch { } } } } }

7 结束语#

本文讲的SourceGenerator和语法分析器,如果你感兴趣但在实验中遇到困难,你可以下载WebApiClient的源代码来直接体验和调试,然后依葫芦画瓢造自己的SourceGenerator。

posted @   jiulang  阅读(3224)  评论(2编辑  收藏  举报
编辑推荐:
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示
CONTENTS