即便 .NET5已经发布了很多年,但许多人依然对这一项令人眼前一亮的新技术 - Source Generators (源代码生成)不怎么熟悉, 如果您有兴趣,可以花5分钟看我白话白话。
起初看到这项技术时甚至引发了我颅内 “高潮” :心说再也不用纠结代理类的种种限制了;通过翻译我们写代码更方便了;进一步提升开源库们的性能;削弱了注解入侵的影响,甚至注释也利用起来了;模板定制?叫T10086吧。
怀揣着对技术大饼的幻想,懒癌晚期的我终于在爬上了椅子下载了 VS 预览版,迫不及待的打开 Demo,正准备尝鲜的时候,突然 VS 编译报错,??,!!,??崩了,奋战1小时后我放弃了,脑潮褪去,再次提起 SG(懒的写 Source Generators )的时候是在一篇 Natasha 文章中:技术很抢眼,应用不值得。
日子混了挺久,直到群里老九说 SG 可用了,其他开源库的作者们三言两语带来了一波新的展望,我便寻思了两晚决定重新试一试。挑了个良辰吉日,卸载预览版,把 VS 升级到最新版,下载 Demo,跑起来了,VS 无异常,控制台秒开,Hello World! 字体清晰可见,我猜这次八成是没啥事了。通过群内讨论与调研,为了不欠技术债,这里我把 SG 技术整体介绍一下:
引用官方的一段话做个技术上的解释:
Source Generator 是 C#开发人员可以编写的一种新型组件,它使您可以做两件事:检索一个 Compilation 对象,该对象表示所有正在编译的用户代码。可以检查此对象,并且您可以编写与正在编译的代码的语法和语义模型一起使用的代码,就像今天的分析器一样。生成可在编译过程中添加到 Compilation 对象的C#源文件。换句话说,您可以在编译代码时提供其他源代码作为编译的输入。源生成器是 .NET Standard 2.0 程序集,由编译器与任何分析器一起加载。它可以在可以加载和运行 .NET Standard 组件的环境中使用。
这里先排除个范围:
1、代码优化
2、日志注入
3、IL 织入
4、调用位重写
以上是 SG 明确不支持的功能,同时,源代码生成器不是分析器,各位不要混淆了。
SG 主要工作单元是 ISourceGenerator 接口,其 Execute 方法参数是携带有 Compilation 对象的上下文,Compilation 是什么呢,如果写过动态编译的同学可能知道,这是在 Emit 发出前构造出的编译信息集合,其中囊括了编译选项,语法树集合,各种编译标识等,Compilation 作为各语言编译信息的抽象结构,是有能力支持 VB / F# 等语言平台的,另外 Compilation 的语法树集合如果允许被替换,那么也可以做很多侵入式操作, 比如内个AOP, 千万别高兴太早,往下看。
默认情况下,实现 ISourceGenerator
的生成器看不到 IDE 的感知,只有在生成的时候才会把信息搜集全,现有版本的 SG 功能有限,想改已写好的代码不可能,最多是结合部分类增加功能,目前所有的操作都是加法, 所以上面说的 AOP 还是得代理。
我们看看官方有什么动态,官方跟一批狠人(grpc / Razor / Blazor等甲方)讨论了新的需求,新版中将使用 初始化上下文 GeneratorInitializationContext 对文件进行监控,还将结合 MSBuild 相关属性完善文件输出等功能。有个挺恶心的问题,文件输出之后调试如何打断点,断点如何起作用,还待解决。这里我多嘴一句,有些新功能要设计成什么样,官方也没定下来。
那么目前来看,官方将从中获取哪些收益呢:
ASP.NET: 减少了启动时间。
Blazor and Razor: 大幅降低了工作负担
序列化 / GRPC / Azure / SWIG 都有用到
为了快一点适应这项技术以便定了个小目标,简单走一遍静态AOP的实现。接下来陈述的问题也许就是目前有些人遇到的问题(以下内容是由群友和开源库作者们及个人挖掘的提问与讨论组成的):
1、就在我写到上面问题冒号的时候,群友问了个问题,为啥我项目跑不起来,没有提示?
经过查看他的截图我确定他应该是不太清楚这个引用结构。准备:
控制台 A ,类库 B;
A 写测试代码, B写代码分析和生成;
A 引用 B;B 引用 Roslyn 相关的库;
注意看A引用节点的属性
<ItemGroup>
<ProjectReference Include="B.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup>
2、如何拿到我定义的类?
首先要实现 ISourceGenerator 接口
1、通过 SourceGeneratorContext 的 AdditionalFiles 读取.cs 文件。
2、通过 SourceGeneratorContext 的 Compilation 获取语法树集合。
3、官方建议给你的类做一些标记,然后通过注册 SyntaxReceiver 实现在树上游走查找标记,来实现记录与过滤,详见属性通知的那个例子。
注:后续的新版本 SourceGeneratorContext 将改名为 GeneratorExecutionContext
这里我表述一下官方推荐的流程,刚接触的人如果不清楚这里肯定会发懵:
1) 实现 ISyntaxReceiver 的 OnVisitSyntaxNode 方法。
2) 写你的 demo , 按 F5 / F6 生成;
3) 你写的代码瞬间被翻译成语法树,编译器马上遍历语法树,走一个节点进一次 OnVisitSyntaxNode 方法,满足你口味的节点就记录下来。
4) Recevier 携带选中的节点,被装进上下文 SourceGeneratorContext 中并进入到 ISourceGenerator 接口的 Execute 方法,此时你的 Recevier 中的信息是所有符合条件的信息,就算你获取了类节点,也是一堆类节点,这里需要手动处理和区分一下。
笔者推荐的流程:
不要把你想改造的类写到 .cs 文件中,因为当前的源代码生成技术不允许你改造现有的类,不允许也不支持,没有语法树替换这一说。允许你添加源代码,允许你操作部分类,就是不允许你改造这个已经存在的 cs 文件。
我个人建议定制模板,让 SG 项目解析模板再添加到你的项目中,也不耽误使用,也不耽误引用,但是对于生态来说,肯定要有统一的规范约束才行。
通过SG生成之后:
public UseMethodModel Show3(ref int a, out string b)
{
UseMethodModelAopProxy.BeforeShow3?.Invoke(this,ref a,out b);
Console.WriteLine("In Show3()");
b = "b";
UseMethodModelAopProxy.AfterShow3?.Invoke(this,ref a,out b);
return this;
}
如下是 AOP 的使用代码:
当前 例子结合了 Natasha 可以做到运行时动态脚本赋值。
运行结果
3、改了不生效啊?
VS 日常玄学操作,右键清理,两个项目都清理。再跑就好了。
4、还是不行啊,爆红。
发生在编译期,要不要编译一下试试?跑通了关掉 VS , 再打开就全绿了。
跑了,还是不行啊?
可能是 SG 项目引发异常了,自己排查吧。
5、不会语法树操作。
可以参考这个项目,有人参与维护的话会发布:https://github.com/dotnet-lab/Papper ,没有就本地引用吧。
6、打包使用?
<ItemGroup>
<!-- Package the generator in the analyzer directory of the nuget package -->
<None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
</ItemGroup>
用 GeneratePathProperty = true 生成对应包的 Pkg 变量,然后输出。
模板以及注释我还没有写Demo, 原谅我没有太多的精力和时间去投入到这件事中,但经过 AOP 项目我可以确信这两个功能可以实现。以上问题已经囊括了大部分尝鲜的坑,各位可以试一试,客观的说,目前 API 还在变,VS 在实验中出现各种灵异事件(目前我的 VS 出现了后遗症)并且官方对这项技术对生态的影响还保持一个观望的态度,所以建议各位稍安勿躁,可以定制计划,实验调研,但上生产还是要谨慎一些!!最后记得点赞,并看一看。
啥也不是,散会!
https://github.com/dotnetcore
打赏一杯酒,削减三分愁。
跟着我们走,脱发包你有。
组织打赏账户为柠檬的账户,请标注「NCC」,并留下您的名字,以下地址可查看收支明细:https://github.com/dotnetcore/Home/blob/master/Statement-of-Income-and-Expense.md
OpenNCC,专注.NET技术的公众号
https://www.dotnetcore.xyz
微信ID:OpenNCC
长按左侧二维码关注
欢迎打赏组织
给予我们更多的支持