增量生成器中级指南
⏱️ 20-30 分钟 | 🔧 中级 | 前置知识:编译基础、代码生成基础
🎯 学习目标
完成本文档后,你将能够:
- [ ] 理解增量生成器的工作原理和优势
- [ ] 掌握管道操作(Select、Where、Combine、Collect)
- [ ] 实现高效的数据缓存机制
- [ ] 使用 ForAttributeWithMetadataName 优化性能
- [ ] 调试和优化增量生成器管道
📖 核心概念
什么是增量生成器?
增量生成器(Incremental Generator) 是 Roslyn 提供的高性能代码生成方式。与传统的 ISourceGenerator 相比,增量生成器使用管道模式和缓存机制,只在输入改变时重新计算,大幅提升性能。
传统生成器 vs 增量生成器
| 特性 | 传统生成器 | 增量生成器 |
|---|---|---|
| 接口 | ISourceGenerator | IIncrementalGenerator |
| 执行时机 | 每次编译都执行 | 只在输入改变时执行 |
| 性能 | 较慢 | 快速(有缓存) |
| 复杂度 | 简单 | 中等 |
| 推荐使用 | ❌ 不推荐 | ✅ 推荐 |
管道模式
增量生成器使用管道模式处理数据,类似于 LINQ:
输入数据 → Select → Where → Combine → Collect → 输出每个操作都会创建一个新的 Provider,数据在管道中流动和转换。
🔧 核心管道操作
操作 1:Select - 数据转换
用途:转换数据,类似于 LINQ 的 Select
使用场景:提取需要的信息、转换数据格式
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 - 数据过滤
用途:过滤数据,只保留满足条件的项
使用场景:筛选特定类型的类、方法等
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 的数据
使用场景:需要同时使用多个数据源
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 - 收集数据
用途:将多个值收集到一个集合中
使用场景:需要一次性处理所有数据
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 ⭐
用途:查找带有特定特性的符号(最高效的方式)
使用场景:基于特性的代码生成
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 模式代码
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 组合多个数据源
场景:根据编译配置和类信息生成不同的代码
[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 聚合数据
场景:收集所有类名,生成一个注册表
[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,支持结构化相等性,缓存机制能正确工作
✅ 推荐做法:
// 使用 record 类型
public record ClassData(string Name, string Namespace);❌ 不推荐做法:
// 使用 class 需要手动实现 IEquatable
public class ClassData
{
public string Name { get; set; }
public string Namespace { get; set; }
}优化 2:使用 ImmutableArray
原因:不可变集合支持高效的相等性比较
✅ 推荐做法:
public record ClassData(
string Name,
ImmutableArray<string> Properties); // 不可变集合❌ 不推荐做法:
public record ClassData(
string Name,
List<string> Properties); // 可变集合,缓存可能失效优化 3:避免在 transform 中执行昂贵操作
❌ 不推荐做法:
var classes = context.SyntaxProvider
.CreateSyntaxProvider(
predicate: (node, _) => node is ClassDeclarationSyntax,
transform: (ctx, _) =>
{
// 昂贵的操作会在每次输入改变时执行
Thread.Sleep(1000);
return ctx.Node;
});✅ 推荐做法:
// 将昂贵操作移到后续的 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
✅ 最佳做法:
// 使用 ForAttributeWithMetadataName(性能最优)
var classes = context.SyntaxProvider
.ForAttributeWithMetadataName(
"MyAttribute",
predicate: (node, _) => node is ClassDeclarationSyntax,
transform: (ctx, _) => ctx.TargetSymbol);❌ 不推荐做法:
// 手动过滤特性(性能较差)
var classes = context.SyntaxProvider
.CreateSyntaxProvider(
predicate: (node, _) => node is ClassDeclarationSyntax,
transform: (ctx, _) => ctx.Node)
.Where((node, _) => node.AttributeLists.Count > 0);📋 Provider 类型
IncrementalValueProvider<T>
表示单个值的 Provider
// CompilationProvider 是 IncrementalValueProvider<Compilation>
IncrementalValueProvider<Compilation> compilation =
context.CompilationProvider;
// 转换为其他单个值
IncrementalValueProvider<string> assemblyName = compilation
.Select((comp, _) => comp.AssemblyName);IncrementalValuesProvider<T>
表示多个值的 Provider
// CreateSyntaxProvider 返回 IncrementalValuesProvider
IncrementalValuesProvider<ClassDeclarationSyntax> classes =
context.SyntaxProvider.CreateSyntaxProvider(
predicate: (node, _) => node is ClassDeclarationSyntax,
transform: (ctx, _) => ctx.Node as ClassDeclarationSyntax);⚠️ 常见陷阱
陷阱 1:忘记实现 IEquatable
❌ 问题:
public class ClassData // 没有实现 IEquatable
{
public string Name { get; set; }
}结果:缓存失效,每次都重新计算
✅ 解决方案:
public record ClassData(string Name); // record 自动实现 IEquatable陷阱 2:使用可变集合
❌ 问题:
public record ClassData(
string Name,
List<string> Properties); // 可变集合结果:相等性比较不正确
✅ 解决方案:
public record ClassData(
string Name,
ImmutableArray<string> Properties); // 不可变集合陷阱 3:在 predicate 中执行复杂逻辑
❌ 问题:
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);✅ 解决方案:
// 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 | 转换数据 | T | U |
Where | 过滤数据 | T | T |
Combine | 组合数据 | T, U | (T, U) |
Collect | 收集数据 | T (多个) | ImmutableArray<T> |
SelectMany | 展开数据 | T | U (多个) |
ForAttributeWithMetadataName | 查找特性 | - | 带特性的符号 |
性能优化检查清单
- [ ] 使用
record类型 - [ ] 使用
ImmutableArray而不是List - [ ] 使用
ForAttributeWithMetadataName查找特性 - [ ]
predicate保持简单 - [ ] 避免在
transform中执行昂贵操作 - [ ] 实现
IEquatable(如果使用 class)
⏭️ 下一步
完成中级学习后,你可以:
🔗 相关资源
最后更新: 2025-02-05