Source Generator 代码生成
大约 10 分钟约 2867 字
Source Generator 代码生成
简介
Source Generator(源代码生成器)是 C# 9.0 / .NET 5 引入的编译时代码生成机制。它允许在编译阶段分析代码并自动生成额外的 C# 源文件,这些生成的代码会自动参与编译。常用于消除样板代码、自动化注册、序列化优化等场景。
特点
基本结构
创建 Source Generator 项目
# 创建类库项目
dotnet new classlib -n MyGenerator
cd MyGenerator
dotnet add package Microsoft.CodeAnalysis.Analyzers
dotnet add package Microsoft.CodeAnalysis.CSharp<!-- MyGenerator.csproj -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" />
</ItemGroup>
</Project>最简单的 Generator
/// <summary>
/// 最简单的 Source Generator
/// </summary>
[Generator]
public class HelloGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
// 注册一个无条件的源代码输出
context.RegisterPostInitializationOutput(ctx =>
{
ctx.AddSource("HelloGenerated.g.cs", @"
namespace AutoGenerated
{
public static class HelloHelper
{
public static string GetMessage() => ""来自 Source Generator 的问候!"";
}
}");
});
}
}基于特性标注生成代码
定义特性
/// <summary>
/// 标记需要自动生成注册代码的特性
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class AutoRegisterAttribute : Attribute
{
public string? InterfaceType { get; }
public AutoRegisterAttribute(string? interfaceType = null)
{
InterfaceType = interfaceType;
}
}Generator 实现
/// <summary>
/// 自动依赖注入注册 Generator
/// 标记 [AutoRegister] 的类自动生成 DI 注册代码
/// </summary>
[Generator]
public class AutoRegisterGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
// 1. 查找所有标记了 AutoRegisterAttribute 的类
var classProvider = context.SyntaxProvider
.CreateSyntaxProvider(
predicate: (node, _) => node is ClassDeclarationSyntax,
transform: (ctx, _) => GetClassInfo(ctx))
.Where(info => info != null);
// 2. 收集所有类信息并生成注册代码
context.RegisterSourceOutput(classProvider.Collect(), (ctx, classes) =>
{
var sb = new StringBuilder();
sb.AppendLine("// <auto-generated/>");
sb.AppendLine("using Microsoft.Extensions.DependencyInjection;");
sb.AppendLine();
sb.AppendLine("namespace AutoGenerated");
sb.AppendLine("{");
sb.AppendLine(" public static class AutoRegisterExtensions");
sb.AppendLine(" {");
sb.AppendLine(" public static void AddAutoRegisteredServices(this IServiceCollection services)");
sb.AppendLine(" {");
foreach (var info in classes!)
{
if (info!.InterfaceType != null)
{
sb.AppendLine($" services.AddScoped<{info.InterfaceType}, {info.FullName}>();");
}
else
{
sb.AppendLine($" services.AddScoped<{info.FullName}>();");
}
}
sb.AppendLine(" }");
sb.AppendLine(" }");
sb.AppendLine("}");
ctx.AddSource("AutoRegister.g.cs", sb.ToString());
});
}
private static ClassInfo? GetClassInfo(GeneratorSyntaxContext context)
{
var classDecl = (ClassDeclarationSyntax)context.Node;
var symbol = context.SemanticModel.GetDeclaredSymbol(classDecl) as INamedTypeSymbol;
if (symbol == null) return null;
// 检查是否有 AutoRegisterAttribute
var attr = symbol.GetAttributes()
.FirstOrDefault(a => a.AttributeClass?.Name == "AutoRegisterAttribute");
if (attr == null) return null;
var interfaceType = attr.ConstructorArguments.FirstOrDefault().Value as string;
return new ClassInfo(
FullName: $"{symbol.ContainingNamespace}.{symbol.Name}",
InterfaceType: interfaceType
);
}
private record ClassInfo(string FullName, string? InterfaceType);
}使用方式
// 在目标项目中引用 Generator
// <ProjectReference Include="..\MyGenerator\MyGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="true" />
// 标记需要自动注册的服务
[AutoRegister("IUserService")]
public class UserService : IUserService { /* ... */ }
[AutoRegister("IOrderService")]
public class OrderService : IOrderService { /* ... */ }
[AutoRegister]
public class EmailService { /* ... */ }
// Program.cs — 一行注册所有
builder.Services.AddAutoRegisteredServices();
// Generator 自动生成的代码:
// services.AddScoped<IUserService, MyServices.UserService>();
// services.AddScoped<IOrderService, MyServices.OrderService>();
// services.AddScoped<MyServices.EmailService>();生成 DTO 映射代码
/// <summary>
/// 自动生成对象映射代码
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class MapFromAttribute : Attribute
{
public Type SourceType { get; }
public MapFromAttribute(Type sourceType) => SourceType = sourceType;
}
// 使用
[MapFrom(typeof(User))]
public class UserDto
{
public string Name { get; set; }
public string Email { get; set; }
}
// Generator 会自动生成:
public static class UserToUserDtoMapper
{
public static UserDto ToDto(this User source) => new()
{
Name = source.Name,
Email = source.Email
};
}增量 Source Generator 深入
增量生成管道
/// <summary>
/// IIncrementalGenerator 的完整管道
/// 1. SyntaxProvider — 提供语法节点和语义信息
/// 2. Collect — 聚合所有节点
/// 3. Select — 转换数据格式
/// 4. Combine — 合并多个数据源
/// 5. RegisterSourceOutput — 生成代码
/// </summary>
[Generator]
public class AdvancedGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
// 第一步:提供语法节点(只处理类声明)
IncrementalValuesProvider<ClassDeclarationSyntax> classDeclarations = context
.SyntaxProvider
.CreateSyntaxProvider(
predicate: static (node, _) => node is ClassDeclarationSyntax,
transform: static (ctx, _) => GetClassDeclaration(ctx))
.Where(static c => c is not null)!;
// 第二步:将语法节点转换为语义信息
IncrementalValuesProvider<ClassModel> classModels = classDeclarations
.Select(static (classDecl, ct) =>
{
var semanticModel = classDecl.SyntaxTree.GetSemanticModel(ct);
var symbol = semanticModel.GetDeclaredSymbol(classDecl);
if (symbol is not INamedTypeSymbol typeSymbol)
return null;
return new ClassModel(
Name: typeSymbol.Name,
Namespace: typeSymbol.ContainingNamespace.ToDisplayString(),
Properties: typeSymbol.GetMembers()
.OfType<IPropertySymbol>()
.Select(p => new PropertyModel(p.Name, p.Type.ToDisplayString()))
.ToList()
);
})
.Where(static m => m is not null)!;
// 第三步:收集所有类并生成代码
context.RegisterSourceOutput(classModels.Collect(), (ctx, classes) =>
{
foreach (var classModel in classes)
{
GenerateCloneMethod(ctx, classModel);
}
});
}
private static ClassDeclarationSyntax? GetClassDeclaration(
GeneratorSyntaxContext context)
{
return context.Node as ClassDeclarationSyntax;
}
private static void GenerateCloneMethod(
SourceProductionContext context, ClassModel model)
{
var sb = new StringBuilder();
sb.AppendLine("// <auto-generated/>");
sb.AppendLine($"namespace {model.Namespace}");
sb.AppendLine("{");
sb.AppendLine($" public partial class {model.Name}");
sb.AppendLine($" {{");
sb.AppendLine($" public {model.Name} Clone()");
sb.AppendLine($" {{");
sb.AppendLine($" return new {model.Name}");
sb.AppendLine($" {{");
foreach (var prop in model.Properties)
{
sb.AppendLine($" {prop.Name} = this.{prop.Name},");
}
sb.AppendLine($" }};");
sb.AppendLine($" }}");
sb.AppendLine($" }}");
sb.AppendLine("}");
context.AddSource($"{model.Name}.g.cs", sb.ToString());
}
private record ClassModel(string Name, string Namespace, List<PropertyModel> Properties);
private record PropertyModel(string Name, string Type);
}诊断报告与错误处理
/// <summary>
/// Generator 中报告编译警告和错误
/// </summary>
[Generator]
public class ValidatingGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
var classes = context.SyntaxProvider
.CreateSyntaxProvider(
predicate: static (node, _) => node is ClassDeclarationSyntax,
transform: static (ctx, _) => GetClassWithAttributes(ctx))
.Where(static c => c is not null)!;
context.RegisterSourceOutput(classes, (ctx, classInfo) =>
{
// 报告警告
if (classInfo.Properties.Count == 0)
{
ctx.ReportDiagnostic(Diagnostic.Create(
new DiagnosticDescriptor(
"SG0001",
"缺少属性",
"类 '{0}' 没有属性,Generator 无法生成有效代码",
"SourceGenerator",
DiagnosticSeverity.Warning,
isEnabledByDefault: true),
classInfo.SyntaxTree.GetRoot().FindNode(classInfo.Span),
classInfo.Name));
return;
}
// 报告错误 — 阻止编译
if (classInfo.HasCircularReference)
{
ctx.ReportDiagnostic(Diagnostic.Create(
new DiagnosticDescriptor(
"SG0002",
"循环引用",
"类 '{0}' 存在循环引用,Generator 无法生成代码",
"SourceGenerator",
DiagnosticSeverity.Error,
isEnabledByDefault: true),
classInfo.SyntaxTree.GetRoot().FindNode(classInfo.Span),
classInfo.Name));
return;
}
// 正常生成代码
ctx.AddSource($"{classInfo.Name}Validator.g.cs", GenerateCode(classInfo));
});
}
private static string GenerateCode(ClassInfo info) => $"// Generated for {info.Name}";
}实际应用场景
| 场景 | 说明 | 代表库 |
|---|---|---|
| 依赖注入注册 | 自动扫描标注的服务并注册 | CommunityToolkit.Mvvm |
| DTO 映射 | 编译时生成映射代码 | Mapster |
| 序列化 | 编译时生成序列化代码 | System.Text.Json |
| MVVM 命令 | 自动生成 ICommand 属性 | CommunityToolkit.Mvvm |
| 代理类 | 生成接口代理/装饰器 | Refit |
| 配置验证 | 编译时检查配置绑定 |
CommunityToolkit.Mvvm 中的 Source Generator
/// <summary>
/// CommunityToolkit.Mvvm — 微软官方 MVVM 工具包大量使用 Source Generator
/// </summary>
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
public partial class MainViewModel : ObservableObject
{
// [ObservableProperty] 生成属性变更通知代码
[ObservableProperty]
private string _title = "";
[ObservableProperty]
private bool _isLoading;
// [RelayCommand] 生成 ICommand 实现
[RelayCommand]
private async Task LoadDataAsync()
{
IsLoading = true;
// 加载数据...
IsLoading = false;
}
[RelayCommand(CanExecute = nameof(CanSave))]
private void Save() { /* ... */ }
private bool CanSave => !string.IsNullOrEmpty(Title);
}优点
缺点
常见问题与最佳实践
调试 Source Generator
/// <summary>
/// 调试 Source Generator 的方法
/// </summary>
// 方法 1:使用 Debugger.Launch
[Generator]
public class DebuggableGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
#if DEBUG
System.Diagnostics.Debugger.Launch();
#endif
// ... 生成逻辑
}
}
// 方法 2:使用 VS 附加进程
// 1. 在 Generator 项目中设置断点
// 2. 编译引用 Generator 的项目
// 3. 使用 VS → 调试 → 附加到进程 → 选择 dotnet 或 MSBuild
// 方法 3:使用 .csproj 的 EmitCompilerGeneratedFiles
// <PropertyGroup>
// <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
// <CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)\generated</CompilerGeneratedFilesOutputPath>
// </PropertyGroup>
// 生成的文件会输出到指定目录,可以直接查看性能优化建议
/// <summary>
/// Source Generator 性能优化要点
/// </summary>
// 1. 使用 IncrementalGenerator 而非 ISourceGenerator
// IncrementalGenerator 只在相关文件变化时重新运行
// 2. 减少 SemanticModel 调用
// SemanticModel 的获取是昂贵的操作
// 在 predicate 中只做语法判断,在 transform 中才获取语义信息
// 3. 使用 Equals/GetHashCode 实现增量缓存
// 为自定义数据类型实现正确的 Equals 和 GetHashCode
private record ClassModel(string Name, string Namespace)
{
// record 自动生成 Equals 和 GetHashCode
// 编译器通过它们判断是否需要重新生成
}
// 4. 避免在 Generator 中做 I/O 操作
// ❌ 不要在 Generator 中读取文件或网络请求
// ✅ 使用 AdditionalFiles 获取额外文件内容
// 5. 生成代码的命名规范
// 使用 .g.cs 后缀(auto-generated)
// 使用 partial class 与用户代码合并
// 文件名包含类名避免冲突
// 6. 代码格式化
// 使用 SyntaxFactory 生成格式化的代码
// 或手动格式化字符串输出项目引用配置
<!-- 目标项目引用 Generator 的正确方式 -->
<ItemGroup>
<!-- Analyzer 引用:告诉编译器这是一个 Generator -->
<ProjectReference Include="..\MyGenerator\MyGenerator.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false" />
</ItemGroup>
<!-- Generator 项目自身不应引用目标项目 -->
<!-- 避免循环依赖 -->总结
Source Generator 是现代 C# 开发的重要工具。它将运行时的反射和代码生成转移到编译时,既提升了性能又保证了类型安全。日常开发中,使用 CommunityToolkit.Mvvm 等 NuGet 包间接使用即可;有特殊需求时再自定义 Generator。
关键知识点
- 先明确这个主题影响的是语法层、运行时层,还是性能与可维护性层。
- 学习时要同时关注语言表面写法和编译器、JIT、GC 等底层行为。
- 真正有价值的是知道“为什么这样写”和“在什么边界下不能这样写”。
- 框架与语言特性类主题要同时理解运行方式和工程组织方式。
项目落地视角
- 把示例改成最小可运行样例,并观察编译输出、运行结果和异常行为。
- 如果它会进入团队代码规范,最好同步补充命名约定、禁用场景和替代方案。
- 涉及性能结论时,优先用 Benchmark 或实际热点链路验证,而不是凭感觉判断。
- 明确项目入口、配置管理、依赖管理、日志和测试策略。
常见误区
- 只记语法糖,不知道底层成本。
- 把适用于小样例的写法直接搬到高并发或大对象场景里。
- 忽略框架版本、语言版本和运行时差异,导致结论失真。
- 把 notebook 或脚本风格直接带入长期维护项目。
进阶路线
- 继续向源码、IL、JIT 行为和 BCL 实现层深入。
- 把知识点和代码评审、性能诊断、面试复盘结合起来。
- 把同类主题做横向对比,例如值类型与引用类型、迭代器与 async 状态机、反射与 Source Generator。
- 继续补齐部署、打包、监控和性能调优能力。
适用场景
- 当你准备把《Source Generator 代码生成》真正落到项目里时,最适合先在一个独立模块或最小样例里验证关键路径。
- 适合在需要理解语言特性、运行时行为或 API 边界时阅读。
- 当代码开始出现性能瓶颈、可维护性问题或语义歧义时,这类主题会直接影响实现质量。
落地建议
- 先写最小可运行样例,再把结论迁移到真实业务代码。
- 同时记录这个特性的收益、限制和替代方案,避免为了“高级”而使用。
- 涉及内存、并发或序列化时,最好配合调试器或基准测试验证。
排错清单
- 先确认问题属于编译期、运行期还是语义误用。
- 检查是否存在隐式转换、装箱拆箱、闭包捕获或上下文切换等隐藏成本。
- 查看异常栈、日志和最小复现代码,优先排除使用姿势问题。
复盘问题
- 如果把《Source Generator 代码生成》放进你的当前项目,最先要验证的输入、输出和失败路径分别是什么?
- 《Source Generator 代码生成》最容易在什么规模、什么边界条件下暴露问题?你会用什么指标或日志去确认?
- 相比默认实现或替代方案,采用《Source Generator 代码生成》最大的收益和代价分别是什么?
