SourceGenerator介绍

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

SourceGenerator未出生时

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

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

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

第一个吃螃蟹的落地项目

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

如何编写xxxSourceGenerator

创建一个netstandard2.0的程序集

<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,接收编译时语法树的遍历

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]特性

[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#代码,会参与编译的");
}
}
}

如何调试xxxSourceGenerator

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

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

在xxxSourceGenerator里加入Debugger.Launch()

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

如何打包发布xxxSourceGenerator

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

分析器的nuget打包

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

install.ps1

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

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

结束语

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

最新文章

  1. Atitit.技术管理者要不要自己做开发??
  2. MySQL 5.7版本sql_mode=only_full_group_by问题
  3. 趣味C程序-HelloWord
  4. 如果把表单数据的校验交给了javascript那么后台还有没有必要对数据做校验呢
  5. c#三层架构登陆实例
  6. scp: command not found如何解决
  7. MapReduce:Shuffle过程的流程
  8. java.text.MessageFormat格式化字符串时的小技巧
  9. c# in deep 之Lambda表达式于LINQ表达式结合后令人惊叹的简洁(2)
  10. HTML5 学习01
  11. Kafka 源代码分析之Log
  12. gis电子地图开发公司面临的挑战和机遇
  13. Java基础—标识符及命名规范
  14. 源码安装xadmin及使用
  15. Egret_简单的封装方法
  16. HTML5 source标签:媒介元素定义媒介资源
  17. 团队第五次 # scrum meeting
  18. Python简单的网络编程
  19. jQuery对标签、类样式、值、文档、DOM对象的操作
  20. nginx + php + centos 6.3

热门文章

  1. Kudu的特点
  2. C++模板元编程----快速排序
  3. 关于_tostring[php]的另类利用
  4. Codis集群相关
  5. Python进阶——什么是上下文管理器?
  6. 服务器安装PVE6.1.2
  7. spring整合sharding-jdbc实现分库分表
  8. java零基础之--【Lombok】简化类设计神器
  9. 事件驱动之JDK观察者模式
  10. 被自己以为的GZIP秀到了