先给不清楚的同学科普下,源代码生成器(Source Generators)其实是Roslyn编译器提供的扩展能力。它允许我们在编译过程中动态分析代码结构并生成新的C源代码文件,这些生成的文件会跟咱们手写的代码一起参与编译。说白了就是在编译前插一脚,自动给项目注入预设好的代码块。
要创建自己的生成器,首先得引用Microsoft.CodeAnalysis.CSharp包。核心类型就两个:ISourceGenerator接口和GeneratorAttribute标签。我习惯先建个控制台项目试试水,记得在csproj里加上<IsRoslynComponent>true</IsRoslynComponent>这个属性。
举个实际例子,假设咱们想自动生成枚举值的描述信息。传统做法要么是手动维护字典,要么用反射动态获取,但性能总会打折扣。用生成器可以这样搞:
这里的关键在于EnumSyntaxReceiver这个类,它负责在编译过程中扫描所有枚举定义。通过语法树分析能找到每个枚举成员及其特性标记,然后动态生成对应的扩展方法。比如根据[Description("状态")]这样的特性自动生成GetDescription()方法。
实际应用中发现个坑:生成器对项目结构有要求。如果A项目依赖B项目的生成器,必须把B项目的输出类型改成控制台应用,否则VS不会自动加载生成器。这个冷知识花了我半天时间才搞明白。
更实用的场景是自动生成API客户端。之前团队维护的WebAPI客户端都是手动同步,经常出现服务端接口已更新而客户端还调着旧接口的情况。用源代码生成器解析Swagger.json,自动生成强类型的HttpClient调用代码后,现在只要更新下JSON文件,所有客户端代码都能自动同步。
性能方面要注意,生成器会在每次编译时执行,所以复杂的语法树分析最好用增量生成方式。比如用RegisterForPostInitialization做预处理,或者用RegisterForSyntaxNotifications注册特定语法节点的监听器。实测在200+个类的项目中,合理优化的生成器只会增加100ms左右的编译时间。
调试技巧也值得一提。在生成器项目里加上<EmbedUntrackedSources>true</EmbedUntrackedSources>,然后就可以用Debugger.Launch()启动调试器。不过更推荐用Log类输出诊断信息,在VS的错误列表里能看到详细的生成日志。
现在团队已经把源代码生成器用到各种场景:自动生成EF Core的DbContext配置、XAML页面的绑定代码、甚至协议栈的序列化类。最直观的感受是代码量减少了40%左右,而且很多运行时错误都转移到了编译期。比如之前经常出现的字段名拼写错误,现在直接用生成器在编译时验证,鼠标悬停就能看到智能提示。
当然也有局限,比如生成的代码不能调试(虽然可以预览),对动态类型的支持比较弱。但总体来看,这确实是C生态里近年来最实用的生产力工具之一。特别适合用来处理那些重复性强、模式固定的编码工作。下次遇到需要批量处理代码的场景,不妨考虑试试这个神器。