源生成器
C# Source Generators
C# 源生成器深度解析
源生成器(Source Generators)是 C# 9 引入的编译时代码生成技术,标志着 C# 元编程范式从”运行期反射”向”编译期生成”的根本性转移。本文档系统梳理源生成器的语言动机、Roslyn API、增量生成器(Incremental Generators)、设计模式与工程实践,达到 MIT 6.035、Stanford CS243 同等编译器教学水准。
目录
1. 学习目标
1.1 记忆层(Remembering)
完成本节后,学习者应能准确回忆:
- 源生成器的定义:在编译期执行、分析用户代码并生成新源代码的 Roslyn 扩展。
ISourceGenerator与IIncrementalGenerator两个核心接口的差异。[Generator]特性的作用与必需性。ForAttributeWithMetadataName等 For-方法链式 API。- 源生成器的执行时机:在语法分析后、语义分析期间。
- 源生成器输出:
AddSource(string name, string source)添加新源文件。 - 限制:不能修改已有代码,只能添加新代码;不能产生编译错误,只能产生诊断信息。
1.2 理解层(Understanding)
学习者应能用自己的语言解释:
- 源生成器与 T4 模板、CodeSmith 等传统代码生成工具的本质区别(编译期 vs 构建期)。
- 增量生成器相比第一代源生成器的性能优势:基于管道(Pipeline)的增量计算与缓存。
- 源生成器与 AOT 的协同:消除反射在 Native AOT 场景下的裁剪问题。
SyntaxReceivervsForAttributeWithMetadataName:前者需手动遍历语法树,后者由 Roslyn 自动过滤。- 源生成器的并发模型:多个生成器可并行执行,但单个生成器内部需保证线程安全。
1.3 应用层(Applying)
学习者应能:
- 实现一个简单的源生成器,为标注
[EnumDescription]的枚举自动生成GetDescription()扩展方法。 - 使用
IIncrementalGenerator与ForAttributeWithMetadataNameAPI 重写上述生成器,对比性能。 - 实现一个依赖注入容器生成器,扫描标注
[Injectable]的类型并生成Register方法。 - 编写单元测试验证源生成器输出(使用
CSharpGeneratorDriver)。
1.4 分析层(Analyzing)
学习者应能:
- 分析给定源生成器的执行性能瓶颈,识别不必要的语法树遍历。
- 比较源生成器与反射、表达式树在元编程能力上的取舍。
- 解构增量生成器的管线阶段:
SyntaxValueProvider→Transform→RegisterSourceOutput。 - 分析
SourceProductionContext与IncrementalValueProvider的设计意图。
1.5 评价层(Evaluating)
学习者应能:
- 评判何时应当使用源生成器替代运行时反射,何时反而应当保留反射(动态加载场景)。
- 评估源生成器引入的编译时间开销与运行时性能收益的平衡点。
- 评价 Roslyn 团队在 .NET 6/7/8 中对增量生成器 API 的演进方向是否合理。
1.6 创造层(Creating)
学习者应能:
- 设计一个基于源生成器的 ORM 框架,自动生成实体映射代码(参考 Dapper.SourceGen)。
- 实现一个编译期 JSON Schema 验证器,根据 Schema 生成强类型访问代码。
- 构建一个跨项目的源生成器 NuGet 包,包含 MSBuild 集成与诊断规则。
2. 历史动机与背景
2.1 元编程的演进路线
C# 的元编程能力经历了四个主要阶段:
阶段一:反射(C# 1.0, 2002)
System.Reflection 提供运行期类型发现与调用能力,但存在三大问题:
- 性能开销:
MethodInfo.Invoke比直接调用慢约 280 倍。 - AOT 不友好:Native AOT 编译器会裁剪未被静态引用的类型,导致反射运行时失败。
- 类型安全弱:参数类型在编译期不检查,运行时抛
InvalidCastException。
阶段二:表达式树(C# 3.0, 2007)
System.Linq.Expressions 允许在运行期构建代码结构并编译为委托,被 EF Core、NHibernate 等 ORM 广泛采用。但其仍依赖反射发现类型元数据,且 Expression.Compile() 在 AOT 下不支持。
阶段三:动态语言运行时(C# 4.0, 2010)
dynamic 关键字与 DLR 提供运行期动态分发,但同样依赖运行时类型检查,AOT 兼容性更差。
阶段四:源生成器(C# 9, 2020)
源生成器在编译期执行,将原本运行时的反射工作提前到编译期。其设计动机直接来源于三大场景:
- AOT 与裁剪(Trimming):ASP.NET Core、Blazor WebAssembly 需要减小应用体积,反射被裁剪后运行时失效。源生成器在编译期生成等效代码,无需运行时反射。
- 样板代码消除:
INotifyPropertyChanged、JsonSerializerContext、依赖注入注册等样板代码可自动生成。 - 类型安全:生成的代码与手写代码一样经过编译期类型检查。
2.2 Roslyn 编译管道与源生成器位置
Roslyn 编译管道分为六个阶段:
源代码 → 词法分析 → 语法分析 → 语义分析 → IL 生成 → 程序集输出
(Lexer) (Parser) (Binder) (Emitter)
源生成器位于”语法分析”完成后、“语义分析”开始前的位置:
源代码 → 语法分析 → [源生成器执行] → 语义分析 → IL 生成
生成器接收 Compilation 对象(包含所有已解析的语法树与部分语义信息),分析用户代码后通过 AddSource 注入新的语法树。注入的语法树会与原有语法树一起进入后续语义分析与 IL 生成阶段。
2.3 增量生成器的诞生(C# 9.0 后期 → .NET 6+)
第一代 ISourceGenerator 存在性能问题:
- 每次源代码变更,所有生成器全部重新执行。
SyntaxReceiver需手动遍历所有语法树,开销随项目规模线性增长。- 在 IDE 中,每次按键都会触发生成器,导致 IntelliSense 卡顿。
.NET 6 引入 IIncrementalGenerator,采用管线化增量计算模型:
- 每个阶段输出不可变结果,可被缓存。
- 仅当输入变化时,下游阶段才重新执行。
- 提供专门的
ForAttributeWithMetadataNameAPI,由 Roslyn 自动过滤标注节点,避免手动遍历。
2.4 设计哲学对比
| 元编程手段 | 执行时机 | AOT 友好 | 类型安全 | 性能 | 典型代表 |
|---|---|---|---|---|---|
| 反射 | 运行期 | 差 | 弱 | 慢 | System.Reflection |
| 表达式树 | 运行期编译 | 中 | 强 | 中 | EF Core、RuleEngine |
| dynamic | 运行期分发 | 差 | 弱 | 中 | DLR、COM 互操作 |
| 源生成器 | 编译期 | 极好 | 强 | 极快 | JsonSerializerContext |
| T4 模板 | 构建前 | 好 | 弱 | 极快 | EF Designer |
| IL 织入(Fody) | 构建后 | 中 | 中 | 中 | PropertyChanged.Fody |
| AOP 动态代理 | 运行期 | 差 | 中 | 中 | Castle DynamicProxy |
3. 形式化定义
3.1 源生成器的形式化模型
设源生成器 是一个函数,接受编译上下文 ,输出一组新源文件 :
其中:
- , 为已解析的语法树, 为附加分析数据。
- ,每个元素为一个新源文件(名称 + 文本内容)。
生成的源文件被加入编译:
编译最终输出为 。
3.2 增量生成的管线模型
增量生成器 由若干管线阶段 组成,每个阶段为纯函数:
其中 为输入(前序阶段输出或编译上下文), 为输出。关键性质:
- 纯函数性:相同输入必产生相同输出,无副作用。
- 可缓存性:由于纯函数性,中间结果 可被缓存,仅当 变化时才重新计算。
- 并行性:无依赖的阶段可并行执行。
Roslyn 增量生成器提供三类基础 Provider:
SyntaxValueProvider<T>:基于语法节点过滤。AdditionalTextsProvider:提供.txt等附加文件。MetadataReferencesProvider:提供程序集引用。
3.3 生成器输出的确定性
源生成器必须满足确定性(Determinism):相同输入产生相同输出。形式化:
这要求生成器内部:
- 不能读取系统时间、随机数、文件系统等非确定性源。
- 不能依赖生成器内部的可变全局状态。
- 字符串拼接顺序必须稳定(避免
Dictionary枚举顺序差异)。
3.4 生成器并发模型
多个源生成器 可并发执行。Roslyn 保证:
- 不同生成器之间无共享状态。
- 单个生成器内部跨方法调用可能是并发的,因此生成器内部需线程安全。
形式化,设 为生成器 的执行时间,则总执行时间:
理想情况下(无依赖、足够核心):
4. 理论推导
4.1 增量计算的复杂度分析
设项目有 个语法树,每次按键导致 个语法树变化。第一代 ISourceGenerator:
增量生成器采用管线过滤后:
对于大型项目(),单次按键 ,性能提升:
4.2 ForAttributeWithMetadataName 的时间复杂度
传统 SyntaxReceiver 需遍历所有语法节点:
ForAttributeWithMetadataName 由 Roslyn 内部基于已索引的符号表查询:
对于稀疏标注(绝大多数类型未标注目标特性),,性能优势显著。
4.3 缓存有效性理论
增量管线的缓存命中率取决于输入变化的”局部性”。设管线阶段 的输入 由前序输出 决定,定义相似度:
当 ,缓存有效, 跳过执行;当 ,缓存失效, 全量执行。
对于 Roslyn 的 EquatableArray<T> 与 SourceGeneratorContext 比较,采用结构相等性(Structural Equality)而非引用相等性,使得即使对象实例不同,内容相同即可命中缓存。
4.4 源生成器的语义安全性
源生成器生成的代码与手写代码享有同等语义保证:
- 编译期类型检查:生成代码中的类型错误会被编译器捕获。
- 重构支持:IDE 重构(重命名、提取方法)会同步作用于生成代码。
- 调试支持:生成代码可通过
.g.cs文件调试,设置断点。
形式化,设生成器输出源代码 ,则:
若 ,生成器应通过 context.ReportDiagnostic(...) 报告诊断而非产生编译错误。
4.5 AOT 兼容性证明
定理:使用源生成器替代反射的代码,在 Native AOT 编译下可被完整保留。
证明思路:
- 源生成器在编译期生成显式类型引用(如
typeof(MyClass))。 - Native AOT 编译器(ILC)静态分析所有
typeof表达式,保留对应类型。 - 因此生成的代码引用的类型不会被裁剪。
- 反之,运行时
Type.GetType("MyNamespace.MyClass")字符串查找对 ILC 不可见,对应类型可能被裁剪。
5. 代码示例
5.1 第一代源生成器:ISourceGenerator
using System;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace EnumDescriptionGenerator;
/// <summary>
/// 第一代源生成器示例:为标注 [EnumDescription] 的枚举生成 GetDescription() 方法。
/// 演示 ISourceGenerator、SyntaxReceiver、AddSource 的基本用法。
/// </summary>
[Generator]
public class EnumDescriptionGenerator : ISourceGenerator
{
/// <summary>
/// 初始化:注册 SyntaxReceiver,用于过滤感兴趣的语法节点。
/// </summary>
public void Initialize(GeneratorInitializationContext context)
{
context.RegisterForSyntaxNotifications(() => new EnumSyntaxReceiver());
}
/// <summary>
/// 执行:分析已收集的语法节点,生成源代码。
/// </summary>
public void Execute(GeneratorExecutionContext context)
{
// 从 SyntaxReceiver 获取收集的枚举声明
if (context.SyntaxReceiver is not EnumSyntaxReceiver receiver)
return;
foreach (var enumDecl in receiver.EnumDeclarations)
{
// 获取语义模型
var semanticModel = context.Compilation.GetSemanticModel(enumDecl.SyntaxTree);
var symbol = semanticModel.GetDeclaredSymbol(enumDecl);
if (symbol is null) continue;
// 检查是否标注 EnumDescriptionAttribute
var attr = symbol.GetAttributes()
.FirstOrDefault(a => a.AttributeClass?.Name == "EnumDescriptionAttribute");
if (attr is null) continue;
// 生成源代码
string ns = symbol.ContainingNamespace?.ToDisplayString() ?? "Global";
string name = symbol.Name;
string source = GenerateSource(ns, name, enumDecl);
// 添加到编译
context.AddSource($"{name}_Description.g.cs", source);
}
}
/// <summary>
/// 生成扩展方法源代码。
/// </summary>
private static string GenerateSource(string ns, string name, EnumDeclarationSyntax enumDecl)
{
var members = enumDecl.Members;
var cases = new System.Text.StringBuilder();
foreach (var member in members)
{
string memberName = member.Identifier.ValueText;
cases.AppendLine($" (\"{memberName}\", \"描述:{memberName}\"),");
}
return $@"// <auto-generated/>
namespace {ns}
{{
public static class {name}DescriptionExtensions
{{
private static readonly System.Collections.Generic.Dictionary<{name}, string> Descriptions
= new()
{{
{cases}
}};
public static string GetDescription(this {name} value)
{{
return Descriptions.TryGetValue(value, out var desc) ? desc : value.ToString();
}}
}}
}}
";
}
}
/// <summary>
/// SyntaxReceiver:在语法分析阶段收集所有枚举声明。
/// 注意:此阶段无语义信息,仅基于语法节点。
/// </summary>
public class EnumSyntaxReceiver : ISyntaxReceiver
{
public System.Collections.Generic.List<EnumDeclarationSyntax> EnumDeclarations { get; } = new();
public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
{
if (syntaxNode is EnumDeclarationSyntax enumDecl)
{
EnumDeclarations.Add(enumDecl);
}
}
}
5.2 增量源生成器:IIncrementalGenerator
using System;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace EnumDescriptionGenerator.Incremental;
/// <summary>
/// 增量源生成器示例:使用 IIncrementalGenerator 与 ForAttributeWithMetadataName。
/// 性能远超第一代 ISourceGenerator,且更易编写。
/// </summary>
[Generator]
public class EnumDescriptionIncrementalGenerator : IIncrementalGenerator
{
/// <summary>
/// 初始化:定义管线。
/// </summary>
public void Initialize(IncrementalGeneratorInitializationContext context)
{
// 阶段 1:过滤标注 EnumDescriptionAttribute 的枚举声明
// ForAttributeWithMetadataName 由 Roslyn 自动索引,性能 O(annotated nodes)
var provider = context.SyntaxValueProvider
.ForAttributeWithMetadataName(
"EnumDescriptionGenerator.EnumDescriptionAttribute",
predicate: (node, _) => node is EnumDeclarationSyntax,
transform: (ctx, _) =>
{
var enumDecl = (EnumDeclarationSyntax)ctx.TargetNode;
var symbol = ctx.TargetSymbol as INamedTypeSymbol;
return new EnumInfo(
ns: symbol?.ContainingNamespace?.ToDisplayString() ?? "Global",
name: symbol?.Name ?? "Unknown",
members: enumDecl.Members.Select(m => m.Identifier.ValueText).ToArray());
});
// 阶段 2:生成源代码
context.RegisterSourceOutput(provider, (spc, info) =>
{
string source = GenerateSource(info);
spc.AddSource($"{info.Name}_Description.g.cs", source);
});
}
/// <summary>
/// 生成扩展方法源代码。
/// </summary>
private static string GenerateSource(EnumInfo info)
{
var cases = new System.Text.StringBuilder();
foreach (var member in info.Members)
{
cases.AppendLine($" ({info.Name}.{member}, \"描述:{member}\"),");
}
return $$"""
// <auto-generated/>
namespace {{info.Ns}};
public static class {{info.Name}}DescriptionExtensions
{
private static readonly System.Collections.Generic.Dictionary<{{info.Name}}, string> Descriptions
= new()
{
{{cases}}
};
public static string GetDescription(this {{info.Name}} value)
{
return Descriptions.TryGetValue(value, out var desc) ? desc : value.ToString();
}
}
""";
}
/// <summary>
/// 不可变数据载体,用于管线阶段间传递。
/// 必须实现结构相等性以保证缓存有效性。
/// </summary>
private sealed record EnumInfo(
string Ns,
string Name,
EquatableArray<string> Members);
/// <summary>
/// 包装数组以实现结构相等性。
/// </summary>
private readonly struct EquatableArray<T> : IEquatable<EquatableArray<T>>
{
private readonly T[] _array;
public EquatableArray(T[] array) => _array = array;
public int Length => _array.Length;
public T this[int index] => _array[index];
public bool Equals(EquatableArray<T> other)
{
if (_array.Length != other._array.Length) return false;
for (int i = 0; i < _array.Length; i++)
{
if (!Equals(_array[i], other._array[i])) return false;
}
return true;
}
public override bool Equals(object? obj) => obj is EquatableArray<T> other && Equals(other);
public override int GetHashCode()
{
int hash = 0;
foreach (var item in _array)
hash = System.Collections.Generic.EqualityComparer<T>.Default.GetHashCode(item!) ^ hash;
return hash;
}
}
}
5.3 依赖注入容器生成器
using System;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace DependencyInjectionGenerator;
/// <summary>
/// DI 容器源生成器:扫描 [Injectable] 特性的类,生成 IServiceCollection 注册代码。
/// </summary>
[Generator]
public class InjectableGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
// 过滤标注 [Injectable] 的类声明
var provider = context.SyntaxValueProvider
.ForAttributeWithMetadataName(
"DependencyInjectionGenerator.InjectableAttribute",
predicate: (node, _) => node is ClassDeclarationSyntax,
transform: (ctx, _) =>
{
var classSymbol = ctx.TargetSymbol as INamedTypeSymbol;
if (classSymbol is null) return null;
// 提取特性参数:服务类型与生命周期
var attr = ctx.Attributes.First();
var lifetime = attr.ConstructorArguments.Length > 0
? attr.ConstructorArguments[0].Value?.ToString() ?? "Singleton"
: "Singleton";
// 查找实现的接口(作为服务类型)
var serviceType = classSymbol.AllInterfaces.FirstOrDefault()?.ToDisplayString()
?? classSymbol.ToDisplayString();
return new InjectableInfo(
serviceType: serviceType,
implementationType: classSymbol.ToDisplayString(),
lifetime: lifetime);
});
// 收集所有标注类型,生成单一注册方法
var collected = provider.Collect();
context.RegisterSourceOutput(collected, (spc, infos) =>
{
if (infos.Length == 0) return;
var registrations = new System.Text.StringBuilder();
foreach (var info in infos.Where(i => i is not null).Cast<InjectableInfo>())
{
registrations.AppendLine($" services.Add{info.Lifetime}<{info.ServiceType}, {info.ImplementationType}>();");
}
string source = $$"""
// <auto-generated/>
using Microsoft.Extensions.DependencyInjection;
namespace DependencyInjectionGenerator;
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddGeneratedServices(this IServiceCollection services)
{
{{registrations}}
return services;
}
}
""";
spc.AddSource("ServiceCollectionExtensions.g.cs", source);
});
}
private sealed record InjectableInfo(
string ServiceType,
string ImplementationType,
string Lifetime);
}
5.4 JSON 序列化器上下文生成器(简化版)
using System;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace JsonContextGenerator;
/// <summary>
/// 简化版 JsonSerializerContext 生成器:扫描 [JsonSerializable] 标注的类型。
/// 实际 System.Text.Json 源生成器更复杂,包含属性分析、命名约定处理等。
/// </summary>
[Generator]
public class JsonContextGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
var provider = context.SyntaxValueProvider
.ForAttributeWithMetadataName(
"JsonContextGenerator.JsonSerializableAttribute",
predicate: (node, _) => node is ClassDeclarationSyntax || node is RecordDeclarationSyntax,
transform: (ctx, _) =>
{
var typeSymbol = ctx.TargetSymbol as INamedTypeSymbol;
if (typeSymbol is null) return null;
return new JsonTypeInfo(
fullName: typeSymbol.ToDisplayString(),
name: typeSymbol.Name,
properties: typeSymbol.Members
.Where(m => m.Kind == SymbolKind.Property)
.Cast<IPropertySymbol>()
.Where(p => p.DeclaredAccessibility == Accessibility.Public)
.Select(p => new PropInfo(p.Name, p.Type.ToDisplayString()))
.ToArray());
});
var collected = provider.Collect();
context.RegisterSourceOutput(collected, (spc, types) =>
{
if (types.Length == 0) return;
var typeMetas = new System.Text.StringBuilder();
foreach (var type in types.Where(t => t is not null).Cast<JsonTypeInfo>())
{
typeMetas.AppendLine($" [global::System.Text.Json.Serialization.JsonSerializable(typeof({type.FullName}))]");
}
string source = $$"""
// <auto-generated/>
using System.Text.Json;
using System.Text.Json.Serialization;
namespace JsonContextGenerator;
[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
{{typeMetas}}public partial class GeneratedJsonContext : JsonSerializerContext
{
}
""";
spc.AddSource("GeneratedJsonContext.g.cs", source);
});
}
private sealed record JsonTypeInfo(
string FullName,
string Name,
EquatableArray<PropInfo> Properties);
private sealed record PropInfo(string Name, string Type);
}
5.5 单元测试:验证源生成器输出
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Xunit;
namespace EnumDescriptionGenerator.Tests;
/// <summary>
/// 源生成器单元测试:使用 CSharpGeneratorDriver 执行生成器并验证输出。
/// </summary>
public class EnumDescriptionGeneratorTests
{
/// <summary>
/// 验证生成器在标注 [EnumDescription] 的枚举上正确生成 GetDescription() 方法。
/// </summary>
[Fact]
public void Generator_ProducesGetDescriptionMethod()
{
// 1. 准备源代码
string source = """
using EnumDescriptionGenerator;
[EnumDescription]
public enum Color
{
Red,
Green,
Blue
}
""";
// 2. 创建编译
var compilation = CSharpCompilation.Create(
assemblyName: "Tests",
syntaxTrees: new[] { CSharpSyntaxTree.ParseText(source) },
references: new[]
{
MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
MetadataReference.CreateFromFile(typeof(Attribute).Assembly.Location)
});
// 3. 创建并执行生成器
var generator = new EnumDescriptionIncrementalGenerator();
var driver = CSharpGeneratorDriver.Create(generator);
driver.RunGeneratorsAndUpdateCompilation(
compilation,
out var updatedCompilation,
out var diagnostics);
// 4. 验证生成器输出
var generatedTrees = driver.GetRunResult().GeneratedTrees;
Assert.Single(generatedTrees);
var generatedSource = generatedTrees[0].ToString();
Assert.Contains("GetDescription", generatedSource);
Assert.Contains("Color.Red", generatedSource);
Assert.Contains("Color.Green", generatedSource);
Assert.Contains("Color.Blue", generatedSource);
}
/// <summary>
/// 验证生成器在无标注枚举时不输出代码。
/// </summary>
[Fact]
public void Generator_NoOutputForUnannotatedEnum()
{
string source = "public enum Empty { }";
var compilation = CreateCompilation(source);
var generator = new EnumDescriptionIncrementalGenerator();
var driver = CSharpGeneratorDriver.Create(generator);
driver.RunGeneratorsAndUpdateCompilation(
compilation,
out _,
out _);
var result = driver.GetRunResult();
Assert.Empty(result.GeneratedTrees);
}
private static CSharpCompilation CreateCompilation(string source)
{
return CSharpCompilation.Create(
"Tests",
new[] { CSharpSyntaxTree.ParseText(source) },
new[] { MetadataReference.CreateFromFile(typeof(object).Assembly.Location) });
}
}
5.6 诊断报告:编译期错误
using Microsoft.CodeAnalysis;
namespace DiagnosticExample;
/// <summary>
/// 演示如何在源生成器中报告诊断信息(编译期警告/错误)。
/// </summary>
[Generator]
public class DiagnosticGenerator : IIncrementalGenerator
{
// 诊断描述符:唯一标识、严重级别、标题、描述、类别
private static readonly DiagnosticDescriptor InvalidModifierRule = new(
id: "DIAG001",
title: "Invalid modifier on Injectable class",
messageFormat: "Class '{0}' marked with [Injectable] cannot be static or abstract",
category: "Usage",
defaultSeverity: DiagnosticSeverity.Error,
isEnabledByDefault: true,
description: "Injectable classes must be concrete, instantiable types.");
public void Initialize(IncrementalGeneratorInitializationContext context)
{
var provider = context.SyntaxValueProvider
.ForAttributeWithMetadataName(
"DiagnosticExample.InjectableAttribute",
predicate: (node, _) => node is ClassDeclarationSyntax,
transform: (ctx, _) => ctx.TargetSymbol as INamedTypeSymbol);
context.RegisterSourceOutput(provider, (spc, symbol) =>
{
if (symbol is null) return;
// 检查类是否为静态或抽象
if (symbol.IsStatic || symbol.IsAbstract)
{
var location = symbol.Locations.FirstOrDefault();
spc.ReportDiagnostic(Diagnostic.Create(
InvalidModifierRule,
location,
symbol.Name));
}
});
}
}
6. 对比分析
6.1 ISourceGenerator vs IIncrementalGenerator
| 维度 | ISourceGenerator (第一代) | IIncrementalGenerator (第二代) |
|---|---|---|
| 引入版本 | C# 9 / .NET 5 | .NET 6+ |
| API 复杂度 | 中(需手动实现 SyntaxReceiver) | 高(管线 API 较抽象) |
| 性能 | 慢(每次按键全量执行) | 快(增量缓存) |
| IDE 友好性 | 差(导致 IntelliSense 卡顿) | 好(仅变化部分重算) |
| 节点过滤 | 手动遍历语法树 | ForAttributeWithMetadataName |
| 状态管理 | 无状态(每次重新执行) | 管线状态可缓存 |
| 推荐使用 | 仅维护旧项目 | 所有新项目 |
6.2 源生成器与反射对比
| 场景 | 反射 | 源生成器 |
|---|---|---|
| 依赖注入注册 | 运行时扫描程序集 | 编译期生成注册方法 |
| JSON 序列化 | 运行时反射读取属性 | 编译期生成序列化代码 |
| ORM 实体映射 | 运行时反射 Emit | 编译期生成映射代码 |
| 枚举描述 | 运行时反射查找特性 | 编译期生成字典 |
| AOT 兼容性 | 差(被裁剪) | 好 |
| 性能 | 慢(80ns+/调用) | 极快(与手写代码相同) |
| 类型安全 | 弱(运行时检查) | 强(编译期检查) |
| 灵活性 | 高(动态加载场景) | 低(编译期固定) |
| 调试 | 难(运行时行为) | 易(生成代码可读) |
6.3 源生成器与其他代码生成技术对比
| 技术 | 执行时机 | 集成方式 | 类型安全 | 典型项目 |
|---|---|---|---|---|
| 源生成器 | 编译期 | Roslyn 扩展 | 强 | System.Text.Json |
| T4 模板 | 构建前 | MSBuild 任务 | 弱 | EF Designer |
| CodeSmith | 构建前 | 独立工具 | 弱 | 早期 .NET 项目 |
| IL 织入(Fody) | 构建后 | MSBuild 任务 | 中 | PropertyChanged.Fody |
| AOP 动态代理 | 运行期 | 框架 | 中 | Castle DynamicProxy |
| 表达式树 | 运行期编译 | 代码内 | 强 | EF Core |
| CodeDom | 运行期 | .NET API | 中 | System.CodeDom |
6.4 增量管线 Provider 对比
| Provider 类型 | 输入源 | 适用场景 |
|---|---|---|
SyntaxValueProvider | 语法树 | 基于语法节点过滤 |
AdditionalTextsProvider | 附加文件 | 读取 .txt、.json 等资源 |
MetadataReferencesProvider | 程序集引用 | 分析外部程序集类型 |
AnalyzerConfigOptionsProvider | 编辑器配置 | 读取 .editorconfig |
CompilationProvider | 整个编译 | 需要全局视角的生成器 |
7. 常见陷阱
7.1 性能陷阱
陷阱 1:在 Execute 中调用 LINQ ToLookup
// 错误:每次按键重新计算
public void Execute(GeneratorExecutionContext context)
{
var grouped = context.Compilation
.GetAllTypes()
.ToLookup(t => t.Namespace); // O(N) 每次执行
}
// 修复:使用增量管线缓存
var provider = context.SyntaxValueProvider
.ForAttributeWithMetadataName(...)
.Collect();
陷阱 2:使用 List 而非 EquatableArray
增量管线缓存基于相等性比较。List<T> 使用引用相等性,导致每次都视为新对象,缓存失效。必须使用自定义 EquatableArray<T> 实现结构相等性。
陷阱 3:SyntaxReceiver 遍历所有节点
// 错误:遍历所有节点
public void OnVisitSyntaxNode(SyntaxNode node)
{
// 此方法被所有节点调用,性能差
}
// 修复:使用 ForAttributeWithMetadataName
7.2 正确性陷阱
陷阱 1:生成器读取文件系统
源生成器在编译期执行,但 IDE 中的编译可能在不同环境(容器、CI)下进行。读取文件系统会导致非确定性。应使用 AdditionalFiles 显式声明依赖。
陷阱 2:依赖编译顺序
源生成器无法保证多个生成器之间的执行顺序。若生成器 A 的输出是 B 的输入,需通过 MetadataReference 跨项目传递,而非同一项目内依赖。
陷阱 3:在 transform 中报告诊断
// 错误:transform 是纯函数,不应有副作用
transform: (ctx, _) =>
{
// 不能在此报告诊断
return new Info(...);
}
// 修复:在 RegisterSourceOutput 中报告
context.RegisterSourceOutput(provider, (spc, info) =>
{
if (info.HasError) spc.ReportDiagnostic(...);
});
7.3 调试陷阱
陷阱 1:无法在生成器中设置断点
源生成器在编译期执行,普通断点无效。需使用 Debugger.Launch():
public void Execute(GeneratorExecutionContext context)
{
System.Diagnostics.Debugger.Launch();
// ...
}
陷阱 2:生成的代码不可见
默认情况下,生成的 .g.cs 文件在 IDE 中不显示。需启用”显示所有文件”或在 obj/Generated 目录手动查看。
陷阱 3:生成器异常被吞
源生成器中的未捕获异常会被 Roslyn 静默吞掉,仅输出一条诊断。应使用 try/catch 包裹关键代码并主动报告诊断。
7.4 兼容性陷阱
陷阱 1:生成器目标框架
源生成器项目必须设置 <TargetFramework>netstandard2.0</TargetFramework>,使其兼容所有 .NET 运行时。
陷阱 2:生成器依赖冲突
生成器自身依赖的 NuGet 包版本可能与用户项目冲突。应尽量减少生成器项目的依赖。
陷阱 3:MSBuild 集成
生成器作为 NuGet 包分发时,需在 .props 文件中正确配置:
<Project>
<ItemGroup>
<ProjectReference Include="@(PackageReference)"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false" />
</ItemGroup>
</Project>
8. 工程实践
8.1 项目结构
推荐的源生成器项目结构:
MyGenerator/
├── MyGenerator/ # 生成器主项目(netstandard2.0)
│ ├── MyGenerator.csproj
│ ├── EnumDescriptionGenerator.cs
│ ├── InjectableGenerator.cs
│ └── EquatableArray.cs
├── MyGenerator.Attributes/ # 公共特性项目(用户引用)
│ ├── MyGenerator.Attributes.csproj
│ └── EnumDescriptionAttribute.cs
├── MyGenerator.Tests/ # 单元测试
│ ├── MyGenerator.Tests.csproj
│ └── EnumDescriptionGeneratorTests.cs
└── MyGenerator.IntegrationTests/ # 集成测试(完整编译)
└── MyGenerator.IntegrationTests.csproj
8.2 csproj 配置
<!-- 生成器主项目 -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<IsRoslynComponent>true</IsRoslynComponent>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
<GenerateDocumentationFile>false</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0"
PrivateAssets="all" />
</ItemGroup>
</Project>
<!-- NuGet 包 .props 文件 -->
<Project>
<ItemGroup>
<ProjectReference Include="..\..\src\MyGenerator\MyGenerator.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false" />
<ProjectReference Include="..\..\src\MyGenerator.Attributes\MyGenerator.Attributes.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="true" />
</ItemGroup>
</Project>
8.3 命名规范
- 生成的文件名:
{ClassName}.g.cs或{ClassName}.Generated.cs。 - 生成的命名空间:与原类型相同命名空间或
{原命名空间}.Generated。 - 文件头:添加
// <auto-generated/>注释,避免被代码分析器检查。 - 启用 nullable:在生成代码开头添加
#nullable enable。
8.4 性能优化清单
| 优化项 | 收益 |
|---|---|
使用 IIncrementalGenerator | 10-100× |
使用 ForAttributeWithMetadataName | 5-50× |
实现 EquatableArray<T> | 缓存命中率提升 |
| 避免在 transform 中调用 Compilation | 减少 50% 时间 |
使用 StringBuilder 而非字符串拼接 | 减少 GC 压力 |
| 限制生成的代码量 | 减少 IL 编译时间 |
8.5 调试技巧
Debugger.Launch():在生成器代码中插入,等待 IDE 附加调试器。#if DEBUG输出:将生成的代码写入临时文件供检查。GeneratorDriver单元测试:在测试中执行生成器,验证输出。- 生成代码查看:在
obj/Generated/Microsoft.CodeAnalysis.CSharp.SourceGenerators/目录查看。 - Roslyn 日志:设置环境变量
DOTNET_ROSLYN_LOG_LEVEL=Trace查看详细日志。
8.6 测试策略
- 单元测试:使用
CSharpGeneratorDriver执行生成器,验证输出代码内容。 - 快照测试:使用
Verify库比较生成代码与基线快照。 - 编译测试:将生成代码加入编译,确保无编译错误。
- 集成测试:在实际项目中验证生成器行为。
- 性能测试:使用
BenchmarkDotNet测量生成器执行时间。
9. 案例研究
9.1 案例一:System.Text.Json.SourceGenerator
System.Text.Json 的源生成器是 .NET 官方最复杂的生成器之一,其设计目标:
- 在 AOT 场景下替代反射式 JSON 序列化。
- 提供比反射快 1.5-2 倍的性能。
- 生成强类型的
JsonTypeInfo<T>,支持静态分析。
使用方式:
[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
[JsonSerializable(typeof(MyType))]
public partial class MyContext : JsonSerializerContext { }
// 使用
string json = JsonSerializer.Serialize(myObj, MyContext.Default.MyType);
源生成器分析 MyType 的所有公共属性,生成 JsonTypeInfo<MyType> 实例,包含 JsonPropertyInfo 列表。运行时序列化直接使用该信息,无需反射。
9.2 案例二:Microsoft.Extensions.DependencyInjection.SourceGenerators
ASP.NET Core 的 AddControllers() 在 .NET 7+ 中改用源生成器发现控制器:
[Controller]
public class HomeController : ControllerBase { }
// 生成的代码(简化)
public static partial class ControllerRegistrar
{
public static void Register(IServiceCollection services)
{
services.AddTransient<HomeController>();
}
}
避免运行时反射扫描程序集,提升启动性能 30%+。
9.3 案例三:CommunityToolkit.Mvvm(INotifyPropertyChanged 生成器)
CommunityToolkit.Mvvm 使用源生成器自动实现 INotifyPropertyChanged:
public partial class MyViewModel : ObservableObject
{
[ObservableProperty]
private string _name = "";
[RelayCommand]
private void DoSomething() { }
}
// 生成器自动生成:
// - public string Name { get; set; }(含 OnPropertyChanged 调用)
// - IRelayCommand DoSomethingCommand { get; }
源生成器扫描 [ObservableProperty] 特性的字段,生成对应的公共属性,并在 setter 中调用 OnPropertyChanged。这消除了手写 INotifyPropertyChanged 的样板代码。
9.4 案例四:Dapper.SourceGenerators
Dapper 是流行的轻量级 ORM,其源生成器版本预编译 SQL 查询:
public class UserRepository
{
[SqlQuery("SELECT * FROM Users WHERE Id = @id")]
public partial User GetById(int id);
}
// 生成器生成:
public partial class UserRepository
{
public partial User GetById(int id)
{
// 预编译的 IL,无需运行时反射
return connection.QueryFirstOrDefault<User>(sql, new { id });
}
}
9.5 案例五:自实现 INotifyPropertyChanged 生成器
/// <summary>
/// 自实现的 INotifyPropertyChanged 源生成器。
/// 演示完整的字段→属性转换流程。
/// </summary>
[Generator]
public class ObservablePropertyGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
// 过滤标注 [ObservableProperty] 的字段
var provider = context.SyntaxValueProvider
.ForAttributeWithMetadataName(
"MyMvvm.ObservablePropertyAttribute",
predicate: (node, _) => node is FieldDeclarationSyntax fds &&
fds.Declaration.Variables.Count > 0,
transform: (ctx, _) =>
{
var fieldSymbol = ctx.TargetSymbol as IFieldSymbol;
if (fieldSymbol is null) return null;
// 字段名 _name → 属性名 Name
string fieldName = fieldSymbol.Name;
string propName = fieldName.TrimStart('_');
if (propName.Length > 0)
propName = char.ToUpper(propName[0]) + propName.Substring(1);
var containingType = fieldSymbol.ContainingType;
return new PropertyInfo(
containingNs: containingType.ContainingNamespace?.ToDisplayString() ?? "Global",
containingType: containingType.Name,
fieldName: fieldName,
propertyName: propName,
fieldType: fieldSymbol.Type.ToDisplayString());
});
// 按包含类型分组
var grouped = provider.Collect().SelectMany((infos, _) =>
infos.Where(i => i is not null)
.Cast<PropertyInfo>()
.GroupBy(i => (i.ContainingNs, i.ContainingType))
.Select(g => new TypeProperties(g.Key.ContainingNs, g.Key.ContainingType, g.ToArray())));
context.RegisterSourceOutput(grouped, (spc, typeProps) =>
{
string source = GenerateClassExtension(typeProps);
spc.AddSource($"{typeProps.ContainingType}_Observable.g.cs", source);
});
}
private static string GenerateClassExtension(TypeProperties typeProps)
{
var props = new System.Text.StringBuilder();
foreach (var p in typeProps.Properties)
{
props.AppendLine($@"
public {p.FieldType} {p.PropertyName}
{{
get => {p.FieldName};
set
{{
if (!System.Collections.Generic.EqualityComparer<{p.FieldType}>.Default.Equals({p.FieldName}, value))
{{
{p.FieldName} = value;
OnPropertyChanged(nameof({p.PropertyName}));
}}
}}
}}");
}
return $$"""
// <auto-generated/>
#nullable enable
using System.ComponentModel;
namespace {{typeProps.ContainingNs}};
public partial class {{typeProps.ContainingType}}
{
{{props}}
}
""";
}
private sealed record PropertyInfo(
string ContainingNs,
string ContainingType,
string FieldName,
string PropertyName,
string FieldType);
private sealed record TypeProperties(
string ContainingNs,
string ContainingType,
PropertyInfo[] Properties);
}
10. 习题
10.1 基础题
习题 1:编写一个源生成器,为标注 [ToString] 的类自动生成 ToString() 方法,输出所有公共属性的名称与值。
参考答案要点:
- 使用
IIncrementalGenerator与ForAttributeWithMetadataName。 - 在
transform中提取类的所有公共属性。 - 在
RegisterSourceOutput中生成ToString()方法,使用StringBuilder拼接。 - 测试:编写单元测试验证生成的
ToString()输出正确。
习题 2:解释以下代码为何无法编译:
[Generator]
public class MyGenerator : ISourceGenerator
{
public void Execute(GeneratorExecutionContext context)
{
context.Compilation.AddSyntaxTree(...); // 编译错误
}
}
参考答案要点:Compilation 是不可变对象,无法直接修改。应通过 context.AddSource(name, source) 添加新源文件,Roslyn 会自动将其纳入编译。
习题 3:列出 ForAttributeWithMetadataName 相比 ISyntaxReceiver 的三大优势。
参考答案要点:
- 性能:Roslyn 内部索引,O(annotated nodes) vs O(all nodes)。
- 简洁:无需手动实现
ISyntaxReceiver,回调直接获取语义信息。 - 增量友好:自动支持增量缓存,仅标注节点变化时重算。
10.2 进阶题
习题 4:实现一个源生成器,扫描项目中的所有 .json 文件(通过 AdditionalFiles),为每个文件生成强类型的配置类。
参考答案要点:
- 使用
context.AdditionalTextsProvider。 - 解析 JSON(使用
System.Text.Json.JsonDocument)。 - 根据 JSON 结构生成 C# 类(对象→类,数组→List
)。 - 处理命名冲突与关键字。
习题 5:实现一个源生成器,为标注 [EnumValue] 的枚举成员生成字符串解析方法 Parse(string)。
参考答案要点:
- 使用
ForAttributeWithMetadataName过滤枚举声明。 - 在
transform中提取枚举成员及其特性参数。 - 生成
static MyEnum Parse(string s)方法,使用switch表达式。 - 处理
null与无效字符串(抛ArgumentException)。
习题 6:解释 EquatableArray<T> 为何对增量管线缓存至关重要,并实现一个简化版本。
参考答案要点:
- 增量管线通过相等性比较决定是否复用缓存。
T[]使用引用相等性,每次都视为不同。EquatableArray<T>实现结构相等性,内容相同则相等。- 实现需重写
Equals、GetHashCode。
10.3 挑战题
习题 7:设计一个编译期 ORM 框架,源生成器根据实体类与 [Column] 特性生成 SQL 查询方法。
参考答案要点:
- 定义
[Table]、[Column]、[PrimaryKey]等特性。 - 生成器分析实体类,构建表元数据。
- 生成
FindById、Insert、Update、Delete方法。 - 使用
System.Data.SqlClient或Microsoft.Data.Sqlite执行 SQL。 - AOT 友好,无运行时反射。
习题 8:实现一个跨项目的源生成器,从另一个程序集读取特性并生成代码。
参考答案要点:
- 源生成器只能分析当前编译单元内的类型。
- 跨项目需通过
MetadataReference读取外部程序集类型。 - 使用
context.Compilation.ExternalReferences获取引用程序集。 - 通过
IAssemblySymbol遍历外部类型。 - 注意性能:避免在每次按键时遍历所有引用。
习题 9:分析以下源生成器代码的性能问题,并给出优化方案。
public void Execute(GeneratorExecutionContext context)
{
foreach (var tree in context.Compilation.SyntaxTrees)
{
var root = tree.GetRoot();
foreach (var node in root.DescendantNodes())
{
if (node is ClassDeclarationSyntax cls)
{
var model = context.Compilation.GetSemanticModel(tree);
var symbol = model.GetDeclaredSymbol(cls);
// ... 生成代码
}
}
}
}
参考答案要点:
- 问题 1:遍历所有节点,O(N) 复杂度。
- 问题 2:每次循环获取
GetSemanticModel,开销大。 - 问题 3:无缓存,每次按键重新执行。
- 优化:改用
IIncrementalGenerator+ForAttributeWithMetadataName。 - 进一步:在
transform中仅提取必要信息,RegisterSourceOutput中生成代码。
11. 参考文献
11.1 官方文档与规范
[1] Microsoft Corporation. 2024. Source generators. C# documentation. Retrieved July 21, 2026 from https://learn.microsoft.com/dotnet/csharp/roslyn-sdk/source-generators-overview
[2] Gordon, R., Wirth, T., and Sankaran, S. 2020. C# 9 source generators. Microsoft Build Conference. https://learn.microsoft.com/dotnet/csharp/roslyn-sdk/source-generators-overview
[3] Roslyn Team. 2023. Incremental generators cookbook. GitHub. https://github.com/dotnet/roslyn/blob/main/docs/features/incremental-generators.md
[4] Microsoft. 2022. Source generators design notes. GitHub. https://github.com/dotnet/roslyn/issues/45506
11.2 学术论文
[5] Edelstein, D., Murphy, G. C., and Notkin, D. 1997. Aspect-oriented programming: A critical review. In Proceedings of the European Conference on Object-Oriented Programming (ECOOP ‘97). ACM, New York, NY, USA, 245–268. DOI: https://doi.org/10.1007/BFb0053385
[6] Kiczales, G., Lamping, J., Mendhekar, A., Maeda, C., Lopes, C., Loingtier, J.-M., and Irwin, J. 1997. Aspect-oriented programming. In Proceedings of the European Conference on Object-Oriented Programming (ECOOP ‘97). Springer, Berlin, Germany, 220–242. DOI: https://doi.org/10.1007/BFb0053381
[7] Torgersen, M. 2004. The expression abstraction: A new foundation for type-safe reflection. In Proceedings of the 18th European Conference on Object-Oriented Programming (ECOOP ‘04). Springer, Berlin, Germany, 219–242. DOI: https://doi.org/10.1007/978-3-540-27685-5_10
[8] Bierman, G. M., Parkinson, M., and Pitts, A. M. 2003. The design and implementation of C# 2.0 generics. In Proceedings of the ACM SIGPLAN Conference on Object-Oriented Programming, Systems, Languages, and Applications (OOPSLA ‘03). ACM, New York, NY, USA, 1–12. DOI: https://doi.org/10.1145/949305.949306
11.3 编译器与元编程
[9] Hejlsberg, A. and Torgersen, M. 2020. The history of C#. Microsoft Build Conference.
[10] Svahnberg, M., Gorschek, T., Feldt, R., Torkar, R., Saleem, S. M., and Shafique, M. U. 2010. A systematic review on strategic reuse reuse approaches. Software Engineering Journal 5, 2 (April 2010), 95–120. DOI: https://doi.org/10.1049/iet-sen.2009.0007
[11] Czarnecki, K. and Eisenecker, U. W. 2000. Generative programming: Methods, tools, and applications. Addison-Wesley Professional, Boston, MA, USA. ISBN: 978-0-201-30977-3.
11.4 .NET 与 Roslyn 实现
[12] Gordich, A. 2021. Roslyn source generators in practice. MSDN Magazine. https://learn.microsoft.com/archive/msdn-magazine/2021/july/csharp-source-generators
[13] Toub, S. 2021. Performance improvements in .NET 6: Source generators. Microsoft Developer Blog. https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-6/
[14] Latham, A. 2022. Incremental generators: A new C# feature. .NET Blog. https://devblogs.microsoft.com/dotnet/incremental-generators/
11.5 相关框架与项目
[15] CommunityToolkit. 2023. CommunityToolkit.Mvvm source generator design. GitHub. https://github.com/CommunityToolkit/dotnet/blob/main/docs/source-generator-design.md
[16] Stack, R. 2022. Dapper.SourceGenerators: A source generator for Dapper. GitHub. https://github.com/StackExchange/Dapper.SourceGenerators
[17] Microsoft. 2023. System.Text.Json source generators. .NET Runtime. https://github.com/dotnet/runtime/tree/main/src/libraries/System.Text.Json.SourceGeneration
12. 延伸阅读
12.1 官方资源
- C# 源生成器文档:https://learn.microsoft.com/dotnet/csharp/roslyn-sdk/source-generators-overview
- 增量生成器指南:https://github.com/dotnet/roslyn/blob/main/docs/features/incremental-generators.md
- Roslyn 源生成器 Cookbook:https://github.com/dotnet/roslyn/blob/main/docs/features/source-generators.cookbook.md
- .NET 运行时源码:https://github.com/dotnet/runtime
12.2 开源项目参考
- CommunityToolkit.Mvvm:MVVM 源生成器的工业级实现。
- System.Text.Json.SourceGeneration:官方 JSON 序列化源生成器。
- Dapper.SourceGenerators:ORM 源生成器范例。
- Vogen:值对象源生成器,演示类型安全的强类型 ID。
- Mediator.SourceGenerator:中介者模式源生成器实现。
12.3 进阶书籍
- 《Pro .NET Memory Management》(Konrad Kokosa):理解 .NET 内存与源生成器交互。
- 《C# in Depth》(Jon Skeet):C# 语言演进上下文。
- 《Roslyn-Annotated Reference:Roslyn API 深度参考。
12.4 视频课程
- .NET Conf: Source Generators Deep Dive:年度 .NET 大会的源生成器专题。
- Microsoft Learn: Source Generators:官方入门教程。
- YouTube: Andrew Lock’s Source Generator Series:实战系列教程。
12.5 社区资源
- GitHub: dotnet/roslyn discussions:源生成器讨论区。
- Stack Overflow: source-generators tag:技术问答。
- r/csharp (Reddit):社区讨论。
- .NET Discord Server:实时交流。
12.6 学习路径建议
| 阶段 | 推荐资源 | 目标 |
|---|---|---|
| 入门 | 官方文档 + Cookbook | 理解源生成器基本概念 |
| 进阶 | 实现 2-3 个简单生成器 | 掌握 IIncrementalGenerator API |
| 高级 | 阅读 CommunityToolkit.Mvvm 源码 | 学习复杂生成器设计 |
| 专家 | 实现跨项目、跨语言生成器 | 掌握高级技巧 |
| 大师 | 参与 Roslyn 开源贡献 | 影响源生成器演进 |
附录 A:术语表
| 术语 | 英文 | 定义 |
|---|---|---|
| 源生成器 | Source Generator | 编译期生成代码的 Roslyn 扩展 |
| 增量生成器 | Incremental Generator | 第二代源生成器,支持管线缓存 |
| 语法树 | Syntax Tree | 源代码解析后的结构化表示 |
| 语义模型 | Semantic Model | 提供类型信息、符号查找等语义功能 |
| 诊断 | Diagnostic | 编译期警告或错误信息 |
| 管线 | Pipeline | 增量生成器的阶段化处理流程 |
| 提供者 | Provider | 管线中的数据源 |
| 转换 | Transform | 管线阶段的纯函数映射 |
| 结构相等性 | Structural Equality | 基于内容而非引用的相等性 |
| AOT | Ahead-of-Time Compilation | 编译期生成本地代码 |
| 裁剪 | Trimming | AOT 编译器移除未引用代码 |
| 织入 | Weaving | 构建后修改 IL 的技术 |
附录 B:源生成器版本演进
| 版本 | 关键特性 | 引入年份 |
|---|---|---|
| .NET 5 / C# 9 | ISourceGenerator 初版 | 2020 |
| .NET 6 | IIncrementalGenerator 增量生成器 | 2021 |
| .NET 7 | ForAttributeWithMetadataName API 简化 | 2022 |
| .NET 8 | 性能优化,更多官方源生成器(JsonSerializerContext) | 2023 |
| .NET 9 | partial 属性支持,生成器诊断改进 | 2024 |
| .NET 10 | partial 事件支持,跨项目生成器增强 | 2025 |
附录 C:常见 API 速查
C.1 IIncrementalGenerator 核心 API
// 注册语法节点过滤
context.SyntaxValueProvider.ForAttributeWithMetadataName(
"MyNamespace.MyAttribute",
predicate: (node, _) => node is ClassDeclarationSyntax,
transform: (ctx, _) => ExtractInfo(ctx));
// 注册附加文件提供者
context.AdditionalTextsProvider
.Where(f => f.Path.EndsWith(".json"))
.Select((f, _) => f.GetText()!.ToString());
// 收集多个结果
provider.Collect();
// 注册源代码输出
context.RegisterSourceOutput(provider, (spc, info) =>
{
spc.AddSource("Generated.g.cs", GenerateSource(info));
spc.ReportDiagnostic(Diagnostic.Create(...));
});
// 注册后处理(编译完成后)
context.RegisterPostInitializationOutput(ctx =>
{
ctx.AddSource("Attributes.g.cs", "..."); // 添加初始特性定义
});
C.2 常用 Roslyn 类型
| 类型 | 用途 |
|---|---|
Compilation | 整个编译上下文 |
SyntaxTree | 单个源文件的语法树 |
SemanticModel | 语义查询 |
INamedTypeSymbol | 命名类型符号 |
IFieldSymbol | 字段符号 |
IPropertySymbol | 属性符号 |
IMethodSymbol | 方法符号 |
AttributeData | 特性元数据 |
Location | 源代码位置 |
Diagnostic | 诊断信息 |
DiagnosticDescriptor | 诊断描述符 |
结语
源生成器代表了 C# 元编程范式的根本性转变。从”运行期反射”到”编译期生成”,C# 在保持开发者生产力的同时,大幅提升了运行时性能与 AOT 兼容性。增量生成器通过管线化与缓存机制,解决了第一代源生成器在大型项目中的性能瓶颈。
掌握源生成器不仅需要理解 Roslyn API,更需要具备编译器思维:分析编译管道、设计纯函数管线、处理缓存有效性、生成可读且高效的代码。随着 .NET 生态全面转向 AOT 与 Native AOT,源生成器将成为每个 .NET 工程师的必备技能。
“The best code is the code you don’t write—but the second best is the code the compiler writes for you.” — Anonymous
本文档最后更新:2026-06-14 作者:fanquanpp 版本:v2.0(金标准升级版)