必须用IIncrementalGenerator,因ISourceGenerator已过时;Initialize中需用IncrementalValueProvider构建依赖链,避免全量遍历语法树导致编译性能下降和IDE失响应。

ISourceGenerator 已过时,现在必须用 IIncrementalGenerator;不这么写,生成器在 .NET 6+ 项目里根本不会触发,连调试断点都进不去。
为什么 Initialize 方法里必须用 IncrementalValueProvider
Roslyn 的增量编译模型不认“遍历所有语法树”这种暴力写法。你如果在 Execute 里直接写 foreach (var tree in context.Compilation.SyntaxTrees),会导致每次编译全量执行,构建变慢,且 IDE 中无法响应编辑(比如改个属性名,生成代码不更新)。
正确做法是用 context.SyntaxProvider 做节点过滤 + context.CompilationProvider 获取语义模型,再链式调用 Combine 和 Select 构建依赖图:
-
SyntaxProvider.CreateSyntaxProvider只对带特定 Attribute 的类声明触发,避免无谓分析 - 用
GetSemanticModelAsync拿到SemanticModel后,才能安全判断类型是否实现某个接口、是否有 public 属性等 - 最终必须走
RegisterSourceOutput,不能手动AddSource——后者绕过增量管道,IDE 补全和错误提示会失效
.csproj 配置里 ReferenceOutputAssembly="false" 是硬性要求
生成器项目被当作 analyzer 引入目标项目时,如果漏设 ReferenceOutputAssembly="false",MSBuild 会把它当成普通依赖项打包进输出目录,运行时可能报 FileNotFoundException 或类型冲突。
目标项目的 .csproj 必须包含:
<ItemGroup>
<ProjectReference Include="..\MyGenerator\MyGenerator.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false" />
</ItemGroup>
注意两点:
-
OutputItemType="Analyzer"让 MSBuild 把它识别为编译器扩展,不是运行时引用 -
ReferenceOutputAssembly="false"确保生成器 DLL 不出现在bin/下,也不参与发布 - 如果 generator 项目用了
<PackageReference>(比如Microsoft.CodeAnalysis.CSharp),目标项目无需再引——重复引用可能引发版本冲突
生成的代码必须是 partial class,且命名空间/类名不能和手写代码冲突
生成器输出的字符串,最终会和用户手写的 .cs 文件一起参与编译。如果生成了非 partial 类,或类名与已有类完全一致,编译器直接报 CS0101: The namespace already contains a definition for 'X'。
常见避坑点:
- 永远用
partial class,哪怕只生成方法体(如自动INotifyPropertyChanged的SetProperty调用) - 类名建议加前缀或后缀,比如手写类叫
Person,生成类叫Person_AutoNotify,再用partial合并 - 生成的文件名(传给
RegisterSourceOutput的第一个参数)要唯一,推荐用哈希或全限定名拼接,避免重名覆盖 - 不要生成
using static或全局using,它们在生成文件中不可靠,容易和目标项目配置冲突
调试 Source Generator 本身比调试生成结果还难
生成器运行在编译进程(vbc.exe 或 csc.exe)里,不是你的程序进程。想断点调试,得用 System.Diagnostics.Debugger.Launch() 并 Attach 到编译器进程——但 VS 默认禁用此行为,且多线程下极易错过。
更实际的做法:
- 把核心逻辑抽成独立方法,在单元测试项目里用
AdhocWorkspace+CSharpCompilation模拟编译上下文,验证语法树解析和代码生成逻辑 - 生成的代码先写死成字符串,用
Console.WriteLine打印出来,确认结构正确后再接入RegisterSourceOutput - 启用 MSBuild 详细日志(
dotnet build -v:d),搜索MyGenerator关键字,看是否被加载、是否触发Initialize - 别依赖 IDE 实时预览——有些生成结果要 Clean + Rebuild 才生效,尤其改了过滤条件之后
最常被忽略的一点:生成器项目的目标框架必须是 netstandard2.0 或 net6.0,不能是 net8.0 ——否则在旧版 SDK 项目里会静默失败,没有任何错误提示。


















