Skip to content

增量生成器中级指南

⏱️ 20-30 分钟 | 🔧 中级 | 前置知识:编译基础、代码生成基础

🎯 学习目标

完成本文档后,你将能够:

  • [ ] 理解增量生成器的工作原理和优势
  • [ ] 掌握管道操作(Select、Where、Combine、Collect)
  • [ ] 实现高效的数据缓存机制
  • [ ] 使用 ForAttributeWithMetadataName 优化性能
  • [ ] 调试和优化增量生成器管道

📖 核心概念

什么是增量生成器?

增量生成器(Incremental Generator) 是 Roslyn 提供的高性能代码生成方式。与传统的 ISourceGenerator 相比,增量生成器使用管道模式和缓存机制,只在输入改变时重新计算,大幅提升性能。

传统生成器 vs 增量生成器

特性传统生成器增量生成器
接口ISourceGeneratorIIncrementalGenerator
执行时机每次编译都执行只在输入改变时执行
性能较慢快速(有缓存)
复杂度简单中等
推荐使用❌ 不推荐✅ 推荐

管道模式

增量生成器使用管道模式处理数据,类似于 LINQ:

输入数据 → Select → Where → Combine → Collect → 输出

每个操作都会创建一个新的 Provider,数据在管道中流动和转换。

🔧 核心管道操作

操作 1:Select - 数据转换

用途:转换数据,类似于 LINQ 的 Select

使用场景:提取需要的信息、转换数据格式

csharp
public void Initialize(IncrementalGeneratorInitializationContext context)
{
    // 获取所有类声明
    var classDeclarations = context.SyntaxProvider
        .CreateSyntaxProvider(
            predicate: (node, _) => node is ClassDeclarationSyntax,
            transform: (ctx, _) => ctx.Node as ClassDeclarationSyntax);
    
    // 使用 Select 转换为类名
    var classNames = classDeclarations
        .Select((classDecl, _) => classDecl?.Identifier.Text);
    
    // 使用 Select 转换为符号
    var classSymbols = classDeclarations
        .Select((classDecl, ct) =>
        {
            var model = ct.SemanticModel;
            return model.GetDeclaredSymbol(classDecl);
        });
}

操作 2:Where - 数据过滤

用途:过滤数据,只保留满足条件的项

使用场景:筛选特定类型的类、方法等

csharp
public void Initialize(IncrementalGeneratorInitializationContext context)
{
    var classDeclarations = context.SyntaxProvider
        .CreateSyntaxProvider(
            predicate: (node, _) => node is ClassDeclarationSyntax,
            transform: (ctx, _) => ctx.Node as ClassDeclarationSyntax);
    
    // 过滤:只保留公共类
    var publicClasses = classDeclarations
        .Where((classDecl, _) =>
        {
            return classDecl.Modifiers
                .Any(m => m.IsKind(SyntaxKind.PublicKeyword));
        });
    
    // 过滤:只保留有特性的类
    var attributedClasses = classDeclarations
        .Where((classDecl, _) =>
        {
            return classDecl.AttributeLists.Count > 0;
        });
}

操作 3:Combine - 组合数据

用途:组合两个 Provider 的数据

使用场景:需要同时使用多个数据源

csharp
public void Initialize(IncrementalGeneratorInitializationContext context)
{
    // 获取编译选项
    var compilationOptions = context.CompilationProvider
        .Select((comp, _) => comp.Options);
    
    // 获取类声明
    var classDeclarations = context.SyntaxProvider
        .CreateSyntaxProvider(
            predicate: (node, _) => node is ClassDeclarationSyntax,
            transform: (ctx, _) => ctx.Node as ClassDeclarationSyntax);
    
    // 组合两个 Provider
    var combined = classDeclarations.Combine(compilationOptions);
    
    // 使用组合的数据
    var result = combined.Select((tuple, _) =>
    {
        var (classDecl, options) = tuple;
        // 根据编译选项和类声明生成代码
        return new { ClassDecl = classDecl, Options = options };
    });
}

操作 4:Collect - 收集数据

用途:将多个值收集到一个集合中

使用场景:需要一次性处理所有数据

csharp
public void Initialize(IncrementalGeneratorInitializationContext context)
{
    var classDeclarations = context.SyntaxProvider
        .CreateSyntaxProvider(
            predicate: (node, _) => node is ClassDeclarationSyntax,
            transform: (ctx, _) => ctx.Node as ClassDeclarationSyntax);
    
    // 收集所有类名到一个集合
    var allClassNames = classDeclarations
        .Select((classDecl, _) => classDecl?.Identifier.Text)
        .Collect();  // 返回 IncrementalValueProvider<ImmutableArray<string>>
    
    // 使用收集的数据
    context.RegisterSourceOutput(allClassNames, (spc, classNames) =>
    {
        // classNames 是 ImmutableArray<string>
        var code = $"// Found {classNames.Length} classes";
        spc.AddSource("AllClasses.g.cs", code);
    });
}

操作 5:ForAttributeWithMetadataName ⭐

用途:查找带有特定特性的符号(最高效的方式)

使用场景:基于特性的代码生成

csharp
public void Initialize(IncrementalGeneratorInitializationContext context)
{
    // 查找所有带 [GenerateToString] 特性的类
    var classesWithAttribute = context.SyntaxProvider
        .ForAttributeWithMetadataName(
            fullyQualifiedMetadataName: "MyNamespace.GenerateToStringAttribute",
            predicate: (node, _) => node is ClassDeclarationSyntax,
            transform: (ctx, _) =>
            {
                // ctx.TargetSymbol 是带特性的符号
                var classSymbol = (INamedTypeSymbol)ctx.TargetSymbol;
                
                // ctx.Attributes 是特性列表
                var attribute = ctx.Attributes[0];
                
                return new ClassInfo(
                    classSymbol.Name,
                    classSymbol.ContainingNamespace.ToDisplayString());
            });
    
    context.RegisterSourceOutput(classesWithAttribute, (spc, info) =>
    {
        var code = GenerateCode(info);
        spc.AddSource($"{info.Name}.g.cs", code);
    });
}

private record ClassInfo(string Name, string Namespace);

private string GenerateCode(ClassInfo info)
{
    return $@"
namespace {info.Namespace}
{{
    partial class {info.Name}
    {{
        // Generated code
    }}
}}";
}

ForAttributeWithMetadataName 的优势

  • ✅ 性能最优(编译器级别优化)
  • ✅ 自动缓存
  • ✅ 类型安全
  • ✅ 代码简洁

💡 实践示例

示例 1:基本增量生成器

场景:为带有 [GenerateBuilder] 特性的类生成 Builder 模式代码

csharp
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Collections.Immutable;
using System.Linq;

[Generator]
public class BuilderGenerator : IIncrementalGenerator
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        // 1. 查找带有 [GenerateBuilder] 特性的类
        var classesWithAttribute = context.SyntaxProvider
            .ForAttributeWithMetadataName(
                "MyNamespace.GenerateBuilderAttribute",
                predicate: (node, _) => node is ClassDeclarationSyntax,
                transform: (ctx, _) => GetClassInfo(ctx))
            .Where(info => info != null);
        
        // 2. 为每个类生成 Builder 代码
        context.RegisterSourceOutput(classesWithAttribute, (spc, info) =>
        {
            var code = GenerateBuilderCode(info);
            spc.AddSource($"{info.ClassName}.Builder.g.cs", code);
        });
    }
    
    private ClassInfo GetClassInfo(GeneratorAttributeSyntaxContext context)
    {
        var classSymbol = (INamedTypeSymbol)context.TargetSymbol;
        
        // 获取所有公共属性
        var properties = classSymbol.GetMembers()
            .OfType<IPropertySymbol>()
            .Where(p => p.DeclaredAccessibility == Accessibility.Public)
            .Select(p => new PropertyInfo(
                p.Name,
                p.Type.ToDisplayString()))
            .ToImmutableArray();
        
        return new ClassInfo(
            classSymbol.Name,
            classSymbol.ContainingNamespace.ToDisplayString(),
            properties);
    }
    
    private string GenerateBuilderCode(ClassInfo info)
    {
        var properties = string.Join("\n        ", 
            info.Properties.Select(p => 
                $"private {p.Type} _{p.Name.ToLower()};"));
        
        var withMethods = string.Join("\n\n        ", 
            info.Properties.Select(p => $@"
public Builder With{p.Name}({p.Type} value)
{{
    _{p.Name.ToLower()} = value;
    return this;
}}"));
        
        var buildAssignments = string.Join("\n                ", 
            info.Properties.Select(p => 
                $"{p.Name} = _{p.Name.ToLower()},"));
        
        return $@"
namespace {info.Namespace}
{{
    public partial class {info.ClassName}
    {{
        public class Builder
        {{
            {properties}
            
            {withMethods}
            
            public {info.ClassName} Build()
            {{
                return new {info.ClassName}
                {{
                    {buildAssignments}
                }};
            }}
        }}
        
        public static Builder CreateBuilder() => new Builder();
    }}
}}";
    }
    
    private record ClassInfo(
        string ClassName,
        string Namespace,
        ImmutableArray<PropertyInfo> Properties);
    
    private record PropertyInfo(string Name, string Type);
}

示例 2:使用 Combine 组合多个数据源

场景:根据编译配置和类信息生成不同的代码

csharp
[Generator]
public class ConfigurableGenerator : IIncrementalGenerator
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        // 数据源 1: 编译配置
        var isDebugBuild = context.CompilationProvider
            .Select((comp, _) => 
                comp.Options.OptimizationLevel == OptimizationLevel.Debug);
        
        // 数据源 2: 类声明
        var classes = context.SyntaxProvider
            .ForAttributeWithMetadataName(
                "MyNamespace.GenerateAttribute",
                predicate: (node, _) => node is ClassDeclarationSyntax,
                transform: (ctx, _) => (INamedTypeSymbol)ctx.TargetSymbol);
        
        // 组合两个数据源
        var combined = classes.Combine(isDebugBuild);
        
        // 根据配置生成不同的代码
        context.RegisterSourceOutput(combined, (spc, data) =>
        {
            var (classSymbol, isDebug) = data;
            
            string code = isDebug
                ? GenerateDebugCode(classSymbol)
                : GenerateReleaseCode(classSymbol);
            
            spc.AddSource($"{classSymbol.Name}.g.cs", code);
        });
    }
    
    private string GenerateDebugCode(INamedTypeSymbol symbol)
    {
        return $@"
namespace {symbol.ContainingNamespace.ToDisplayString()}
{{
    partial class {symbol.Name}
    {{
        // Debug 模式:包含详细日志
        private void Log(string message)
        {{
            System.Diagnostics.Debug.WriteLine(
                $""[{symbol.Name}] {{message}}"");
        }}
    }}
}}";
    }
    
    private string GenerateReleaseCode(INamedTypeSymbol symbol)
    {
        return $@"
namespace {symbol.ContainingNamespace.ToDisplayString()}
{{
    partial class {symbol.Name}
    {{
        // Release 模式:优化性能,无日志
        [System.Runtime.CompilerServices.MethodImpl(
            System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
        private void Log(string message) {{ }}
    }}
}}";
    }
}

示例 3:使用 Collect 聚合数据

场景:收集所有类名,生成一个注册表

csharp
[Generator]
public class RegistryGenerator : IIncrementalGenerator
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        // 获取所有带特性的类
        var classes = context.SyntaxProvider
            .ForAttributeWithMetadataName(
                "MyNamespace.RegisterAttribute",
                predicate: (node, _) => node is ClassDeclarationSyntax,
                transform: (ctx, _) =>
                {
                    var symbol = (INamedTypeSymbol)ctx.TargetSymbol;
                    return new ClassInfo(
                        symbol.Name,
                        symbol.ContainingNamespace.ToDisplayString());
                });
        
        // 收集所有类信息
        var allClasses = classes.Collect();
        
        // 生成注册表代码
        context.RegisterSourceOutput(allClasses, (spc, classList) =>
        {
            var code = GenerateRegistry(classList);
            spc.AddSource("ClassRegistry.g.cs", code);
        });
    }
    
    private string GenerateRegistry(ImmutableArray<ClassInfo> classes)
    {
        // 按命名空间分组
        var grouped = classes
            .GroupBy(c => c.Namespace)
            .OrderBy(g => g.Key);
        
        var registrations = string.Join("\n            ", 
            classes.Select(c => 
                $"{{ \"{c.Namespace}.{c.Name}\", typeof({c.Namespace}.{c.Name}) }},"));
        
        return $@"
using System;
using System.Collections.Generic;

namespace Generated
{{
    public static class ClassRegistry
    {{
        public static readonly Dictionary<string, Type> RegisteredClasses = 
            new Dictionary<string, Type>
            {{
                {registrations}
            }};
        
        public static int Count => {classes.Length};
    }}
}}";
    }
    
    private record ClassInfo(string Name, string Namespace);
}

🚀 性能优化

优化 1:使用 record 类型

原因:record 自动实现 IEquatable,支持结构化相等性,缓存机制能正确工作

推荐做法

csharp
// 使用 record 类型
public record ClassData(string Name, string Namespace);

不推荐做法

csharp
// 使用 class 需要手动实现 IEquatable
public class ClassData
{
    public string Name { get; set; }
    public string Namespace { get; set; }
}

优化 2:使用 ImmutableArray

原因:不可变集合支持高效的相等性比较

推荐做法

csharp
public record ClassData(
    string Name,
    ImmutableArray&lt;string&gt; Properties);  // 不可变集合

不推荐做法

csharp
public record ClassData(
    string Name,
    List&lt;string&gt; Properties);  // 可变集合,缓存可能失效

优化 3:避免在 transform 中执行昂贵操作

不推荐做法

csharp
var classes = context.SyntaxProvider
    .CreateSyntaxProvider(
        predicate: (node, _) => node is ClassDeclarationSyntax,
        transform: (ctx, _) =>
        {
            // 昂贵的操作会在每次输入改变时执行
            Thread.Sleep(1000);
            return ctx.Node;
        });

推荐做法

csharp
// 将昂贵操作移到后续的 Select 中
var classes = context.SyntaxProvider
    .CreateSyntaxProvider(
        predicate: (node, _) => node is ClassDeclarationSyntax,
        transform: (ctx, _) => ctx.Node);

var processed = classes.Select((node, _) =>
{
    // 这里的操作会被缓存
    return ExpensiveOperation(node);
});

优化 4:使用 ForAttributeWithMetadataName

最佳做法

csharp
// 使用 ForAttributeWithMetadataName(性能最优)
var classes = context.SyntaxProvider
    .ForAttributeWithMetadataName(
        "MyAttribute",
        predicate: (node, _) => node is ClassDeclarationSyntax,
        transform: (ctx, _) => ctx.TargetSymbol);

不推荐做法

csharp
// 手动过滤特性(性能较差)
var classes = context.SyntaxProvider
    .CreateSyntaxProvider(
        predicate: (node, _) => node is ClassDeclarationSyntax,
        transform: (ctx, _) => ctx.Node)
    .Where((node, _) => node.AttributeLists.Count > 0);

📋 Provider 类型

IncrementalValueProvider<T>

表示单个值的 Provider

csharp
// CompilationProvider 是 IncrementalValueProvider&lt;Compilation&gt;
IncrementalValueProvider&lt;Compilation&gt; compilation = 
    context.CompilationProvider;

// 转换为其他单个值
IncrementalValueProvider&lt;string&gt; assemblyName = compilation
    .Select((comp, _) => comp.AssemblyName);

IncrementalValuesProvider<T>

表示多个值的 Provider

csharp
// CreateSyntaxProvider 返回 IncrementalValuesProvider
IncrementalValuesProvider&lt;ClassDeclarationSyntax&gt; classes = 
    context.SyntaxProvider.CreateSyntaxProvider(
        predicate: (node, _) => node is ClassDeclarationSyntax,
        transform: (ctx, _) => ctx.Node as ClassDeclarationSyntax);

⚠️ 常见陷阱

陷阱 1:忘记实现 IEquatable

问题

csharp
public class ClassData  // 没有实现 IEquatable
{
    public string Name { get; set; }
}

结果:缓存失效,每次都重新计算

解决方案

csharp
public record ClassData(string Name);  // record 自动实现 IEquatable

陷阱 2:使用可变集合

问题

csharp
public record ClassData(
    string Name,
    List&lt;string&gt; Properties);  // 可变集合

结果:相等性比较不正确

解决方案

csharp
public record ClassData(
    string Name,
    ImmutableArray&lt;string&gt; Properties);  // 不可变集合

陷阱 3:在 predicate 中执行复杂逻辑

问题

csharp
var classes = context.SyntaxProvider
    .CreateSyntaxProvider(
        predicate: (node, _) =>
        {
            // 复杂的逻辑会影响性能
            if (node is ClassDeclarationSyntax classDecl)
            {
                return classDecl.AttributeLists.Count > 0 &&
                       classDecl.Modifiers.Any(m => m.IsKind(SyntaxKind.PublicKeyword));
            }
            return false;
        },
        transform: (ctx, _) => ctx.Node);

解决方案

csharp
// predicate 保持简单,复杂逻辑放在 Where 中
var classes = context.SyntaxProvider
    .CreateSyntaxProvider(
        predicate: (node, _) => node is ClassDeclarationSyntax,
        transform: (ctx, _) => ctx.Node as ClassDeclarationSyntax)
    .Where((classDecl, _) =>
        classDecl.AttributeLists.Count > 0 &&
        classDecl.Modifiers.Any(m => m.IsKind(SyntaxKind.PublicKeyword)));

🎓 快速参考

管道操作速查表

操作用途输入输出
Select转换数据TU
Where过滤数据TT
Combine组合数据T, U(T, U)
Collect收集数据T (多个)ImmutableArray<T>
SelectMany展开数据TU (多个)
ForAttributeWithMetadataName查找特性-带特性的符号

性能优化检查清单

  • [ ] 使用 record 类型
  • [ ] 使用 ImmutableArray 而不是 List
  • [ ] 使用 ForAttributeWithMetadataName 查找特性
  • [ ] predicate 保持简单
  • [ ] 避免在 transform 中执行昂贵操作
  • [ ] 实现 IEquatable (如果使用 class)

⏭️ 下一步

完成中级学习后,你可以:

🔗 相关资源


最后更新: 2025-02-05

基于 MIT 许可发布