最佳实践
增量生成器管道的最佳实践和反模式
📋 文档信息
| 属性 | 值 |
|---|---|
| 难度 | 中级 |
| 阅读时间 | 25 分钟 |
| 前置知识 | 管道操作、缓存机制 |
| 相关文档 | 性能优化 |
🎯 学习目标
完成本文档后,你将能够:
- ✅ 遵循管道开发最佳实践
- ✅ 避免常见的反模式
- ✅ 编写高性能的管道代码
- ✅ 编写可维护的管道代码
📚 快速导航
| 章节 | 说明 |
|---|---|
| 推荐做法 | 最佳实践 |
| 反模式 | 应该避免的做法 |
| 常见错误 | 常见错误和解决方案 |
推荐做法
✅ 做法 1: 使用 ForAttributeWithMetadataName
原因: 性能最优,自动过滤和缓存。
csharp
// ✅ 好的做法:使用 ForAttributeWithMetadataName
var classes = context.SyntaxProvider
.ForAttributeWithMetadataName(
"MyAttribute",
predicate: (node, _) => node is ClassDeclarationSyntax,
transform: (ctx, _) => ctx.TargetSymbol);
// ❌ 不好的做法:使用 CreateSyntaxProvider + 手动检查特性
var classes = context.SyntaxProvider
.CreateSyntaxProvider(
predicate: (node, _) => node is ClassDeclarationSyntax,
transform: (ctx, _) =>
{
var symbol = ctx.SemanticModel.GetDeclaredSymbol(ctx.Node);
if (!symbol.GetAttributes().Any(a => a.AttributeClass?.Name == "MyAttribute"))
return null;
return symbol;
})
.Where(s => s != null);✅ 做法 2: 使用 record 类型
原因: 自动实现 IEquatable,支持缓存。
csharp
// ✅ 好的做法:使用 record
public record ClassData(string Name, ImmutableArray<string> Properties);
// ❌ 不好的做法:使用 class 但不实现 IEquatable
public class ClassData
{
public string Name { get; set; }
public List<string> Properties { get; set; }
}✅ 做法 3: 使用 ImmutableArray
原因: 值相等性,支持缓存。
csharp
// ✅ 好的做法:使用 ImmutableArray
public record ClassData(
string Name,
ImmutableArray<string> Properties);
// ❌ 不好的做法:使用 List
public record ClassData(
string Name,
List<string> Properties); // 引用相等性✅ 做法 4: 提前过滤
原因: 减少不必要的计算。
csharp
// ✅ 好的做法:在 predicate 中过滤
var classes = context.SyntaxProvider
.CreateSyntaxProvider(
predicate: (node, _) =>
{
if (node is not ClassDeclarationSyntax classDecl)
return false;
// 提前过滤:只接受公共类
return classDecl.Modifiers.Any(m => m.IsKind(SyntaxKind.PublicKeyword));
},
transform: (ctx, _) => ctx.Node);
// ❌ 不好的做法:在 transform 中过滤
var classes = context.SyntaxProvider
.CreateSyntaxProvider(
predicate: (node, _) => node is ClassDeclarationSyntax,
transform: (ctx, _) =>
{
var classDecl = (ClassDeclarationSyntax)ctx.Node;
// 晚过滤:已经创建了语义模型
if (!classDecl.Modifiers.Any(m => m.IsKind(SyntaxKind.PublicKeyword)))
return null;
return classDecl;
})
.Where(c => c != null);✅ 做法 5: 缓存昂贵操作
原因: 避免重复计算。
csharp
// ✅ 好的做法:缓存昂贵操作
var globalConfig = context.CompilationProvider
.Select((comp, _) => PerformExpensiveOperation())
.WithTrackingName("GlobalConfig");
var classes = context.SyntaxProvider
.CreateSyntaxProvider(...);
var combined = classes.Combine(globalConfig);
// ❌ 不好的做法:在 transform 中执行昂贵操作
var classes = context.SyntaxProvider
.CreateSyntaxProvider(
predicate: (node, _) => node is ClassDeclarationSyntax,
transform: (ctx, _) =>
{
// 每个类都执行昂贵操作
var result = PerformExpensiveOperation();
return (ctx.Node, result);
});✅ 做法 6: 添加 WithTrackingName
原因: 便于性能分析和调试。
csharp
// ✅ 好的做法:添加跟踪名称
var classes = context.SyntaxProvider
.CreateSyntaxProvider(...)
.WithTrackingName("FindClasses");
var filtered = classes
.Where(...)
.WithTrackingName("FilterClasses");
// ❌ 不好的做法:没有跟踪名称
var classes = context.SyntaxProvider
.CreateSyntaxProvider(...);
var filtered = classes.Where(...);✅ 做法 7: 避免不必要的 Collect
原因: Collect 会等待所有值,影响性能。
csharp
// ✅ 好的做法:直接处理每个类
var classes = context.SyntaxProvider
.ForAttributeWithMetadataName(...);
context.RegisterSourceOutput(classes, (spc, classInfo) =>
{
// 为每个类生成代码
var code = GenerateCode(classInfo);
spc.AddSource($"{classInfo.Name}.g.cs", code);
});
// ❌ 不好的做法:不必要的 Collect
var classes = context.SyntaxProvider
.ForAttributeWithMetadataName(...);
var allClasses = classes.Collect(); // 等待所有类
context.RegisterSourceOutput(allClasses, (spc, classInfos) =>
{
// 为每个类生成代码(但已经等待了所有类)
foreach (var classInfo in classInfos)
{
var code = GenerateCode(classInfo);
spc.AddSource($"{classInfo.Name}.g.cs", code);
}
});反模式
❌ 反模式 1: 在 transform 中执行昂贵操作
问题: 影响性能。
csharp
// ❌ 不好的做法
var classes = context.SyntaxProvider
.CreateSyntaxProvider(
predicate: (node, _) => node is ClassDeclarationSyntax,
transform: (ctx, _) =>
{
// 昂贵的操作会在每次输入改变时执行
Thread.Sleep(1000);
return ctx.Node;
});正确做法: 将昂贵操作移到后续的 Select 中,并使用缓存。
csharp
// ✅ 好的做法
var globalData = context.CompilationProvider
.Select((comp, _) =>
{
// 只执行一次
Thread.Sleep(1000);
return "result";
});
var classes = context.SyntaxProvider
.CreateSyntaxProvider(...);
var combined = classes.Combine(globalData);❌ 反模式 2: 使用可变集合
问题: 缓存失效。
csharp
// ❌ 不好的做法:使用 List
public record ClassData(
string Name,
List<string> Properties); // 可变集合
// 问题演示
var props = new List<string> { "A", "B" };
var data1 = new ClassData("User", props);
var data2 = new ClassData("User", props);
Console.WriteLine(data1 == data2); // True(引用相同)
props.Add("C"); // 修改 List
// 问题:GetHashCode 可能不一致
var hash1 = data1.GetHashCode();
props.Add("D");
var hash2 = data1.GetHashCode();
Console.WriteLine(hash1 == hash2); // 可能是 False正确做法: 使用 ImmutableArray。
csharp
// ✅ 好的做法:使用 ImmutableArray
public record ClassData(
string Name,
ImmutableArray<string> Properties); // 不可变集合❌ 反模式 3: 忘记实现 IEquatable
问题: 缓存失效,每次都重新计算。
csharp
// ❌ 不好的做法:class 但不实现 IEquatable
public class ClassData
{
public string Name { get; set; }
public ImmutableArray<string> Properties { get; set; }
}
// 问题:引用不同,即使内容相同
var data1 = new ClassData { Name = "User", Properties = ImmutableArray.Create("A") };
var data2 = new ClassData { Name = "User", Properties = ImmutableArray.Create("A") };
Console.WriteLine(data1.Equals(data2)); // False
// 结果:缓存失效,重新生成代码正确做法: 使用 record 或手动实现 IEquatable。
csharp
// ✅ 好的做法:使用 record
public record ClassData(
string Name,
ImmutableArray<string> Properties);❌ 反模式 4: 过度使用 Collect
问题: 影响性能,失去增量优势。
csharp
// ❌ 不好的做法:不必要的 Collect
var classes = context.SyntaxProvider
.ForAttributeWithMetadataName(...);
var allClasses = classes.Collect(); // 等待所有类
context.RegisterSourceOutput(allClasses, (spc, classInfos) =>
{
foreach (var classInfo in classInfos)
{
// 为每个类生成独立的文件
var code = GenerateCode(classInfo);
spc.AddSource($"{classInfo.Name}.g.cs", code);
}
});正确做法: 直接处理每个值。
csharp
// ✅ 好的做法:直接处理
var classes = context.SyntaxProvider
.ForAttributeWithMetadataName(...);
context.RegisterSourceOutput(classes, (spc, classInfo) =>
{
// 直接为每个类生成代码
var code = GenerateCode(classInfo);
spc.AddSource($"{classInfo.Name}.g.cs", code);
});常见错误
🐛 错误 1: 忘记实现 IEquatable
描述: 数据结构没有实现 IEquatable,导致缓存失效。
解决方案: 使用 record 类型或手动实现 IEquatable。
csharp
// ✅ 解决方案 1: 使用 record
public record ClassData(string Name, ImmutableArray<string> Properties);
// ✅ 解决方案 2: 手动实现 IEquatable
public class ClassData : IEquatable<ClassData>
{
public string Name { get; set; }
public ImmutableArray<string> Properties { get; set; }
public bool Equals(ClassData other)
{
if (other is null) return false;
return Name == other.Name &&
Properties.SequenceEqual(other.Properties);
}
public override bool Equals(object obj) => Equals(obj as ClassData);
public override int GetHashCode()
{
var hash = new HashCode();
hash.Add(Name);
foreach (var prop in Properties)
hash.Add(prop);
return hash.ToHashCode();
}
}🐛 错误 2: 使用可变集合
描述: 使用 List 等可变集合,导致缓存比较不正确。
解决方案: 使用 ImmutableArray 等不可变集合。
csharp
// ✅ 解决方案
public record ClassData(
string Name,
ImmutableArray<string> Properties); // 使用 ImmutableArray🐛 错误 3: 在 predicate 中访问语义模型
描述: 在 predicate 中访问语义模型,影响性能。
解决方案: 只在 transform 中访问语义模型。
csharp
// ❌ 错误做法
var classes = context.SyntaxProvider
.CreateSyntaxProvider(
predicate: (node, ct) =>
{
// 错误:在 predicate 中访问语义模型
var model = ct.SemanticModel;
return node is ClassDeclarationSyntax;
},
transform: (ctx, _) => ctx.Node);
// ✅ 正确做法
var classes = context.SyntaxProvider
.CreateSyntaxProvider(
predicate: (node, _) => node is ClassDeclarationSyntax,
transform: (ctx, _) =>
{
// 正确:在 transform 中访问语义模型
var model = ctx.SemanticModel;
return ctx.Node;
});🐛 错误 4: GetHashCode 不一致
描述: GetHashCode 依赖可变字段。
解决方案: 使用不可变类型或只读属性。
csharp
// ❌ 错误做法
public class ClassData : IEquatable<ClassData>
{
public string Name { get; set; } // 可变
public override int GetHashCode()
{
return Name?.GetHashCode() ?? 0; // 依赖可变字段
}
}
// ✅ 解决方案:使用 record(不可变)
public record ClassData(string Name);最佳实践检查清单
开发前检查
- [ ] 使用
ForAttributeWithMetadataName而不是CreateSyntaxProvider - [ ] 使用
record类型 - [ ] 使用
ImmutableArray而不是List - [ ] 在
predicate中提前过滤 - [ ] 缓存昂贵操作到
CompilationProvider - [ ] 添加
WithTrackingName进行性能分析
代码审查检查
- [ ] 没有在
transform中执行昂贵操作 - [ ] 没有使用可变集合
- [ ] 正确实现了 IEquatable
- [ ] 没有不必要的
Collect操作 - [ ] 没有在
predicate中访问语义模型 - [ ] GetHashCode 不依赖可变字段
🔑 关键要点
性能最佳实践
- 使用 ForAttributeWithMetadataName (10-100x 提升)
- 提前过滤 (减少 90% 的计算)
- 使用 record + ImmutableArray (提升缓存效率)
- 缓存昂贵操作 (避免重复计算)
- 添加 WithTrackingName (识别瓶颈)
可维护性最佳实践
- 使用 record 类型 (简化代码)
- 添加 WithTrackingName (便于调试)
- 使用结构化日志 (便于诊断)
- 编写单元测试 (验证正确性)
- 添加文档注释 (便于理解)
🔗 相关资源
🚀 下一步
最后更新: 2026-02-05