前置知识: C#

源生成器

52 minAdvanced2026/6/14

C# Source Generators

C# 源生成器深度解析

源生成器(Source Generators)是 C# 9 引入的编译时代码生成技术,标志着 C# 元编程范式从”运行期反射”向”编译期生成”的根本性转移。本文档系统梳理源生成器的语言动机、Roslyn API、增量生成器(Incremental Generators)、设计模式与工程实践,达到 MIT 6.035、Stanford CS243 同等编译器教学水准。

目录

  1. 学习目标
  2. 历史动机与背景
  3. 形式化定义
  4. 理论推导
  5. 代码示例
  6. 对比分析
  7. 常见陷阱
  8. 工程实践
  9. 案例研究
  10. 习题
  11. 参考文献
  12. 延伸阅读

1. 学习目标

1.1 记忆层(Remembering)

完成本节后,学习者应能准确回忆:

  • 源生成器的定义:在编译期执行、分析用户代码并生成新源代码的 Roslyn 扩展。
  • ISourceGeneratorIIncrementalGenerator 两个核心接口的差异。
  • [Generator] 特性的作用与必需性。
  • ForAttributeWithMetadataName 等 For-方法链式 API。
  • 源生成器的执行时机:在语法分析后、语义分析期间。
  • 源生成器输出:AddSource(string name, string source) 添加新源文件。
  • 限制:不能修改已有代码,只能添加新代码;不能产生编译错误,只能产生诊断信息。

1.2 理解层(Understanding)

学习者应能用自己的语言解释:

  • 源生成器与 T4 模板、CodeSmith 等传统代码生成工具的本质区别(编译期 vs 构建期)。
  • 增量生成器相比第一代源生成器的性能优势:基于管道(Pipeline)的增量计算与缓存。
  • 源生成器与 AOT 的协同:消除反射在 Native AOT 场景下的裁剪问题。
  • SyntaxReceiver vs ForAttributeWithMetadataName:前者需手动遍历语法树,后者由 Roslyn 自动过滤。
  • 源生成器的并发模型:多个生成器可并行执行,但单个生成器内部需保证线程安全。

1.3 应用层(Applying)

学习者应能:

  • 实现一个简单的源生成器,为标注 [EnumDescription] 的枚举自动生成 GetDescription() 扩展方法。
  • 使用 IIncrementalGeneratorForAttributeWithMetadataName API 重写上述生成器,对比性能。
  • 实现一个依赖注入容器生成器,扫描标注 [Injectable] 的类型并生成 Register 方法。
  • 编写单元测试验证源生成器输出(使用 CSharpGeneratorDriver)。

1.4 分析层(Analyzing)

学习者应能:

  • 分析给定源生成器的执行性能瓶颈,识别不必要的语法树遍历。
  • 比较源生成器与反射、表达式树在元编程能力上的取舍。
  • 解构增量生成器的管线阶段:SyntaxValueProviderTransformRegisterSourceOutput
  • 分析 SourceProductionContextIncrementalValueProvider 的设计意图。

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 提供运行期类型发现与调用能力,但存在三大问题:

  1. 性能开销MethodInfo.Invoke 比直接调用慢约 280 倍。
  2. AOT 不友好:Native AOT 编译器会裁剪未被静态引用的类型,导致反射运行时失败。
  3. 类型安全弱:参数类型在编译期不检查,运行时抛 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)

源生成器在编译期执行,将原本运行时的反射工作提前到编译期。其设计动机直接来源于三大场景:

  1. AOT 与裁剪(Trimming):ASP.NET Core、Blazor WebAssembly 需要减小应用体积,反射被裁剪后运行时失效。源生成器在编译期生成等效代码,无需运行时反射。
  2. 样板代码消除INotifyPropertyChangedJsonSerializerContext、依赖注入注册等样板代码可自动生成。
  3. 类型安全:生成的代码与手写代码一样经过编译期类型检查。

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,采用管线化增量计算模型:

  • 每个阶段输出不可变结果,可被缓存。
  • 仅当输入变化时,下游阶段才重新执行。
  • 提供专门的 ForAttributeWithMetadataName API,由 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 源生成器的形式化模型

设源生成器 GG 是一个函数,接受编译上下文 CC,输出一组新源文件 ΔS\Delta S

G:CΔSG: C \to \Delta S

其中:

  • C=T1,T2,,Tn,A1,,AmC = \langle T_1, T_2, \ldots, T_n, A_1, \ldots, A_m \rangleTiT_i 为已解析的语法树,AjA_j 为附加分析数据。
  • ΔS={(namek,sourcek)k=1,2,,p}\Delta S = \{ (name_k, source_k) \mid k = 1, 2, \ldots, p \},每个元素为一个新源文件(名称 + 文本内容)。

生成的源文件被加入编译:

C=CΔSC' = C \cup \Delta S

编译最终输出为 Compile(C)Compile(C')

3.2 增量生成的管线模型

增量生成器 GincG_{\text{inc}} 由若干管线阶段 P1,P2,,PkP_1, P_2, \ldots, P_k 组成,每个阶段为纯函数:

Pi:IiOiP_i: I_i \to O_i

其中 IiI_i 为输入(前序阶段输出或编译上下文),OiO_i 为输出。关键性质:

  • 纯函数性:相同输入必产生相同输出,无副作用。
  • 可缓存性:由于纯函数性,中间结果 OiO_i 可被缓存,仅当 IiI_i 变化时才重新计算。
  • 并行性:无依赖的阶段可并行执行。

Roslyn 增量生成器提供三类基础 Provider:

  1. SyntaxValueProvider<T>:基于语法节点过滤。
  2. AdditionalTextsProvider:提供 .txt 等附加文件。
  3. MetadataReferencesProvider:提供程序集引用。

3.3 生成器输出的确定性

源生成器必须满足确定性(Determinism):相同输入产生相同输出。形式化:

C1,C2:C1=C2    G(C1)=G(C2)\forall C_1, C_2: C_1 = C_2 \implies G(C_1) = G(C_2)

这要求生成器内部:

  • 不能读取系统时间、随机数、文件系统等非确定性源。
  • 不能依赖生成器内部的可变全局状态。
  • 字符串拼接顺序必须稳定(避免 Dictionary 枚举顺序差异)。

3.4 生成器并发模型

多个源生成器 G1,G2,,GkG_1, G_2, \ldots, G_k 可并发执行。Roslyn 保证:

  • 不同生成器之间无共享状态。
  • 单个生成器内部跨方法调用可能是并发的,因此生成器内部需线程安全。

形式化,设 TiT_i 为生成器 GiG_i 的执行时间,则总执行时间:

Ttotali=1kTiT_{\text{total}} \leq \sum_{i=1}^{k} T_i

理想情况下(无依赖、足够核心):

Ttotal=max(T1,T2,,Tk)T_{\text{total}} = \max(T_1, T_2, \ldots, T_k)


4. 理论推导

4.1 增量计算的复杂度分析

设项目有 NN 个语法树,每次按键导致 ΔN\Delta N 个语法树变化。第一代 ISourceGenerator

Told=O(N)每次按键T_{\text{old}} = O(N) \quad \text{每次按键}

增量生成器采用管线过滤后:

Tnew=O(ΔN+pipeline overhead)每次按键T_{\text{new}} = O(\Delta N + \text{pipeline overhead}) \quad \text{每次按键}

对于大型项目(N=10000N = 10000),单次按键 ΔN=1\Delta N = 1,性能提升:

Speedup=NΔN=10000×\text{Speedup} = \frac{N}{\Delta N} = 10000 \times

4.2 ForAttributeWithMetadataName 的时间复杂度

传统 SyntaxReceiver 需遍历所有语法节点:

Treceiver=O(nodes)T_{\text{receiver}} = O(|\text{nodes}|)

ForAttributeWithMetadataName 由 Roslyn 内部基于已索引的符号表查询:

Tattr=O(annotated nodes)T_{\text{attr}} = O(|\text{annotated nodes}|)

对于稀疏标注(绝大多数类型未标注目标特性),annotated nodesnodes|\text{annotated nodes}| \ll |\text{nodes}|,性能优势显著。

4.3 缓存有效性理论

增量管线的缓存命中率取决于输入变化的”局部性”。设管线阶段 PiP_i 的输入 IiI_i 由前序输出 Oi1O_{i-1} 决定,定义相似度:

sim(Ii(t),Ii(t1))=Ii(t)Ii(t1)Ii(t)Ii(t1)\text{sim}(I_i^{(t)}, I_i^{(t-1)}) = \frac{|I_i^{(t)} \cap I_i^{(t-1)}|}{|I_i^{(t)} \cup I_i^{(t-1)}|}

sim1\text{sim} \to 1,缓存有效,PiP_i 跳过执行;当 sim0\text{sim} \to 0,缓存失效,PiP_i 全量执行。

对于 Roslyn 的 EquatableArray<T>SourceGeneratorContext 比较,采用结构相等性(Structural Equality)而非引用相等性,使得即使对象实例不同,内容相同即可命中缓存。

4.4 源生成器的语义安全性

源生成器生成的代码与手写代码享有同等语义保证:

  • 编译期类型检查:生成代码中的类型错误会被编译器捕获。
  • 重构支持:IDE 重构(重命名、提取方法)会同步作用于生成代码。
  • 调试支持:生成代码可通过 .g.cs 文件调试,设置断点。

形式化,设生成器输出源代码 sΔSs \in \Delta S,则:

TypeCheck(s)=true    s 可被纳入编译\text{TypeCheck}(s) = \text{true} \implies s \text{ 可被纳入编译}

TypeCheck(s)=false\text{TypeCheck}(s) = \text{false},生成器应通过 context.ReportDiagnostic(...) 报告诊断而非产生编译错误。

4.5 AOT 兼容性证明

定理:使用源生成器替代反射的代码,在 Native AOT 编译下可被完整保留。

证明思路

  1. 源生成器在编译期生成显式类型引用(如 typeof(MyClass))。
  2. Native AOT 编译器(ILC)静态分析所有 typeof 表达式,保留对应类型。
  3. 因此生成的代码引用的类型不会被裁剪。
  4. 反之,运行时 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 APISystem.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 性能优化清单

优化项收益
使用 IIncrementalGenerator10-100×
使用 ForAttributeWithMetadataName5-50×
实现 EquatableArray<T>缓存命中率提升
避免在 transform 中调用 Compilation减少 50% 时间
使用 StringBuilder 而非字符串拼接减少 GC 压力
限制生成的代码量减少 IL 编译时间

8.5 调试技巧

  1. Debugger.Launch():在生成器代码中插入,等待 IDE 附加调试器。
  2. #if DEBUG 输出:将生成的代码写入临时文件供检查。
  3. GeneratorDriver 单元测试:在测试中执行生成器,验证输出。
  4. 生成代码查看:在 obj/Generated/Microsoft.CodeAnalysis.CSharp.SourceGenerators/ 目录查看。
  5. Roslyn 日志:设置环境变量 DOTNET_ROSLYN_LOG_LEVEL=Trace 查看详细日志。

8.6 测试策略

  1. 单元测试:使用 CSharpGeneratorDriver 执行生成器,验证输出代码内容。
  2. 快照测试:使用 Verify 库比较生成代码与基线快照。
  3. 编译测试:将生成代码加入编译,确保无编译错误。
  4. 集成测试:在实际项目中验证生成器行为。
  5. 性能测试:使用 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() 方法,输出所有公共属性的名称与值。

参考答案要点

  • 使用 IIncrementalGeneratorForAttributeWithMetadataName
  • 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 的三大优势。

参考答案要点

  1. 性能:Roslyn 内部索引,O(annotated nodes) vs O(all nodes)。
  2. 简洁:无需手动实现 ISyntaxReceiver,回调直接获取语义信息。
  3. 增量友好:自动支持增量缓存,仅标注节点变化时重算。

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> 实现结构相等性,内容相同则相等。
  • 实现需重写 EqualsGetHashCode

10.3 挑战题

习题 7:设计一个编译期 ORM 框架,源生成器根据实体类与 [Column] 特性生成 SQL 查询方法。

参考答案要点

  • 定义 [Table][Column][PrimaryKey] 等特性。
  • 生成器分析实体类,构建表元数据。
  • 生成 FindByIdInsertUpdateDelete 方法。
  • 使用 System.Data.SqlClientMicrosoft.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 官方资源

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基于内容而非引用的相等性
AOTAhead-of-Time Compilation编译期生成本地代码
裁剪TrimmingAOT 编译器移除未引用代码
织入Weaving构建后修改 IL 的技术

附录 B:源生成器版本演进

版本关键特性引入年份
.NET 5 / C# 9ISourceGenerator 初版2020
.NET 6IIncrementalGenerator 增量生成器2021
.NET 7ForAttributeWithMetadataName API 简化2022
.NET 8性能优化,更多官方源生成器(JsonSerializerContext)2023
.NET 9partial 属性支持,生成器诊断改进2024
.NET 10partial 事件支持,跨项目生成器增强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(金标准升级版)

返回入门指南