Source Generator 是 C# 9+ 的编译时代码生成机制,通过实现 IIncrementalGenerator 在 Roslyn 编译阶段注入 partial 类型,用于自动实现接口、生成 DTO、注入日志等,不修改源码、不支持运行时或已编译程序集。

Source Generator 是什么,它能做什么
Source Generator 是 C# 9+ 提供的编译时代码生成机制,不是运行时反射或模板引擎。它在 Roslyn 编译器执行语法分析阶段介入,通过实现 IIncrementalGenerator 接口,在不修改原始源码的前提下,向编译流水线注入新的 C# 类型(如 partial class)。常见用途包括:自动实现 INotifyPropertyChanged、从 JSON Schema 生成 DTO、为标记了特定 Attribute 的类注入日志/验证逻辑。
它不能替代运行时代码生成(如 Reflection.Emit),也不处理已编译的程序集——只作用于当前项目正在编译的源码树。
创建一个最简 Source Generator 项目
需要两个分离的项目:一个是 generator 本身(类库),另一个是使用它的目标项目(通常为 .NET 6+ SDK 风格的 Console 或 Library)。
实操步骤:
- 新建类库项目,目标框架设为
netstandard2.0(兼容性最好),添加 NuGet 包:Microsoft.CodeAnalysis.CSharp(v4.0+)、Microsoft.CodeAnalysis.Analyzers(可选但推荐) - 添加对
System.Collections.Immutable的引用(.NET 5+ 已内置,旧版需显式添加) - 实现
IIncrementalGenerator,重写Initialize方法;不要用传统ISourceGenerator(已过时) - 在目标项目中,以
ProjectReference方式引用 generator 项目,并设置OutputItemType="Analyzer"和ReferenceOutputAssembly="false"
关键配置示例(目标项目的 .csproj 中):
为什么 Initialize 方法里要用 IncrementalValueProvider
Roslyn 的增量编译模型要求 generator 显式声明依赖项(如语法树、语义模型、特定 Attribute),否则无法触发重生成。直接遍历 compilation.SyntaxTrees 会破坏增量性,导致每次编译都全量执行,拖慢构建速度。
正确做法是使用 context.SyntaxProvider + context.CompilationProvider 构建链式管道:
-
CreateSyntaxProvider过滤出你关心的语法节点(比如所有带[AutoLog]的方法声明) -
GetSemanticModelAsync在后续阶段获取语义信息(如判断该方法是否在 partial class 中) - 最终用
RegisterSourceOutput输出生成的代码字符串
错误写法(破坏增量性):
foreach (var tree in context.Compilation.SyntaxTrees) { ... }正确起点示例:
context.RegisterSourceOutput(
context.SyntaxProvider
.CreateSyntaxProvider(
predicate: static (s, _) => s is MethodDeclarationSyntax m &&
m.AttributeLists.Any(al => al.Attributes.Any(a => a.Name.ToString() == "AutoLog")),
transform: static (ctx, _) => ctx.Node as MethodDeclarationSyntax)
.Where(m => m != null),
Execute);生成的代码必须是 partial 且命名空间/类名严格匹配
Source Generator 输出的代码会被 Roslyn 当作普通源文件参与编译,但它不能定义“全新”的非 partial 类——否则会和用户手写的类冲突,报 CS0101: The namespace 'X' already contains a definition for 'Y'。
所以必须遵守:
- 生成的类型必须声明为
partial class、partial struct或partial record - 命名空间、类名、泛型参数数量与用户代码完全一致(大小写敏感)
- 不能生成构造函数或已有成员的重复定义(如已有
public void Foo(),就不能再生成同签名方法) - 若需注入字段/方法,建议加唯一后缀(如
__autoLogHandler)避免命名污染
生成内容示例(字符串拼接,非 Roslyn SyntaxFactory):
var code = $$"""
namespace {{@namespace}};
partial class {{className}}
{
private void <>__AutoLog_{{methodName}}() { /* ... */ }
}
""";最容易被忽略的是命名空间嵌套层级和 using 指令缺失——generator 不自动继承目标文件的 using,所有类型引用必须写全名或手动拼 using 行。










