性能优化最佳实践
本文档介绍源生成器的性能优化最佳实践,包括增量生成、早期过滤和缓存策略。
📋 文档信息
| 属性 | 值 |
|---|---|
| 难度 | 中级 - 高级 |
| 阅读时间 | 20 分钟 |
| 前置知识 | 增量生成器、性能分析 |
🎯 学习目标
学完本文档后,你将能够:
- ✅ 使用增量生成器提升性能
- ✅ 实施早期过滤策略
- ✅ 使用 record 类型支持缓存
- ✅ 优化字符串操作和内存使用
- ✅ 实施高级缓存策略
⚡ 使用增量生成器
1. 始终使用 IIncrementalGenerator
✅ 推荐:增量生成器
csharp
// ✅ 推荐:增量生成器
[Generator]
public class FastGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
// 增量管道自动缓存和优化
var classes = context.SyntaxProvider
.ForAttributeWithMetadataName(
"GenerateAttribute",
predicate: (node, _) => node is ClassDeclarationSyntax,
transform: (ctx, _) => GetClassInfo(ctx));
context.RegisterSourceOutput(classes, GenerateCode);
}
}
// ❌ 避免:传统生成器
[Generator]
public class SlowGenerator : ISourceGenerator
{
public void Execute(GeneratorExecutionContext context)
{
// 每次都重新分析所有代码,性能差
foreach (var tree in context.Compilation.SyntaxTrees)
{
// ...
}
}
public void Initialize(GeneratorInitializationContext context) { }
}性能对比:
- 增量生成器:只处理变化的代码,性能提升 10-100 倍
- 传统生成器:每次都重新分析,性能差
🔍 早期过滤
2. 在 predicate 中进行语法层面的快速过滤
✅ 推荐:早期过滤
csharp
public void Initialize(IncrementalGeneratorInitializationContext context)
{
var classes = context.SyntaxProvider
.ForAttributeWithMetadataName(
"GenerateAttribute",
// ✅ 在语法层面快速过滤
predicate: (node, _) =>
{
if (node is not ClassDeclarationSyntax cls)
return false;
// 检查是否是 partial 类
if (!cls.Modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword)))
return false;
// 检查是否是 public 类
if (!cls.Modifiers.Any(m => m.IsKind(SyntaxKind.PublicKeyword)))
return false;
return true;
},
transform: (ctx, _) => GetClassInfo(ctx));
}❌ 避免:在 transform 中过滤
csharp
// ❌ 性能差:在 transform 中进行复杂过滤
var classes = context.SyntaxProvider
.ForAttributeWithMetadataName(
"GenerateAttribute",
predicate: (node, _) => true, // 不过滤,所有节点都进入 transform
transform: (ctx, _) =>
{
var symbol = ctx.TargetSymbol as INamedTypeSymbol;
// 在这里过滤,需要语义分析,性能差
if (symbol == null) return null;
if (!symbol.IsPartialDefinition()) return null;
if (symbol.DeclaredAccessibility != Accessibility.Public) return null;
return GetClassInfo(ctx);
});📦 使用 record 类型支持缓存
3. record 类型自动实现值相等性
✅ 推荐:使用 record 类型
csharp
// ✅ record 类型自动实现值相等性,支持缓存
public record ClassInfo(
string Name,
string Namespace,
ImmutableArray<PropertyInfo> Properties);
public record PropertyInfo(
string Name,
string Type,
bool IsNullable);❌ 避免:使用 class 类型
csharp
// ❌ class 类型使用引用相等性,不支持缓存
public class ClassInfo
{
public string Name { get; set; }
public string Namespace { get; set; }
public List<PropertyInfo> Properties { get; set; }
}为什么 record 更好:
- 自动实现值相等性(
Equals和GetHashCode) - 增量生成器可以正确缓存
- 不可变性保证线程安全
🚫 避免不必要的语义分析
4. 只在需要时进行语义分析
✅ 推荐:只在需要时进行语义分析
csharp
var classes = context.SyntaxProvider
.ForAttributeWithMetadataName(
"GenerateAttribute",
predicate: (node, _) =>
{
// ✅ 只使用语法信息,不需要语义分析
return node is ClassDeclarationSyntax cls &&
cls.Modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword));
},
transform: (ctx, _) =>
{
// ✅ 只在这里进行语义分析
var symbol = (INamedTypeSymbol)ctx.TargetSymbol;
return new ClassInfo(symbol.Name);
});📝 使用 StringBuilder 构建代码
5. StringBuilder 性能优化
✅ 推荐:使用 StringBuilder
csharp
private string GenerateCode(ClassInfo classInfo)
{
var sb = new StringBuilder();
sb.AppendLine($"namespace {classInfo.Namespace}");
sb.AppendLine("{");
sb.AppendLine($" public partial class {classInfo.Name}");
sb.AppendLine(" {");
foreach (var property in classInfo.Properties)
{
sb.AppendLine($" public {property.Type} {property.Name} {{ get; set; }}");
}
sb.AppendLine(" }");
sb.AppendLine("}");
return sb.ToString();
}❌ 避免:使用字符串拼接
csharp
// ❌ 性能差
private string GenerateCode(ClassInfo classInfo)
{
string code = "namespace " + classInfo.Namespace + "\n{\n";
code += " public partial class " + classInfo.Name + "\n {\n";
foreach (var property in classInfo.Properties)
{
code += " public " + property.Type + " " + property.Name + " { get; set; }\n";
}
code += " }\n}";
return code;
}🎓 高级性能优化技巧
1. 使用增量值提供器(IncrementalValueProvider)
✅ 推荐:链式组合提供器
csharp
public void Initialize(IncrementalGeneratorInitializationContext context)
{
// 步骤 1: 收集编译信息
var compilationInfo = context.CompilationProvider
.Select((compilation, _) => new CompilationInfo
{
AssemblyName = compilation.AssemblyName,
Language = compilation.Language
});
// 步骤 2: 收集类信息
var classInfos = context.SyntaxProvider
.ForAttributeWithMetadataName(
"GenerateAttribute",
predicate: (node, _) => node is ClassDeclarationSyntax,
transform: (ctx, _) => GetClassInfo(ctx));
// 步骤 3: 组合数据
var combined = compilationInfo.Combine(classInfos.Collect());
// 步骤 4: 生成代码
context.RegisterSourceOutput(combined, (spc, data) =>
{
var (compInfo, classes) = data;
foreach (var classInfo in classes)
{
var code = GenerateCode(compInfo, classInfo);
spc.AddSource($"{classInfo.Name}.g.cs", code);
}
});
}❌ 避免:在 RegisterSourceOutput 中进行复杂计算
csharp
// ❌ 不好:在输出阶段进行复杂计算
context.RegisterSourceOutput(classInfos, (spc, classInfo) =>
{
// 这些计算应该在 Select 或 Transform 中完成
var compilation = GetCompilation(); // 昂贵操作
var additionalInfo = AnalyzeClass(classInfo); // 昂贵操作
var code = GenerateCode(classInfo, additionalInfo);
spc.AddSource($"{classInfo.Name}.g.cs", code);
});2. 使用 Collect() 批量处理
✅ 推荐:批量处理相关项
csharp
public void Initialize(IncrementalGeneratorInitializationContext context)
{
var classInfos = context.SyntaxProvider
.ForAttributeWithMetadataName(
"GenerateAttribute",
predicate: (node, _) => node is ClassDeclarationSyntax,
transform: (ctx, _) => GetClassInfo(ctx));
// 收集所有类信息
var allClasses = classInfos.Collect();
// 批量生成代码
context.RegisterSourceOutput(allClasses, (spc, classes) =>
{
// 生成索引文件
var indexCode = GenerateIndexFile(classes);
spc.AddSource("Index.g.cs", indexCode);
// 生成每个类的代码
foreach (var classInfo in classes)
{
var code = GenerateClassCode(classInfo);
spc.AddSource($"{classInfo.Name}.g.cs", code);
}
});
}
private string GenerateIndexFile(ImmutableArray<ClassInfo> classes)
{
var sb = new StringBuilder();
sb.AppendLine("// Auto-generated index");
sb.AppendLine("public static class GeneratedIndex");
sb.AppendLine("{");
sb.AppendLine(" public static readonly string[] AllClasses = new[]");
sb.AppendLine(" {");
foreach (var classInfo in classes)
{
sb.AppendLine($" \"{classInfo.Name}\",");
}
sb.AppendLine(" };");
sb.AppendLine("}");
return sb.ToString();
}3. 使用 WithComparer 优化缓存
✅ 推荐:实现 IEquatable 和提供比较器
csharp
// 数据模型实现 IEquatable
public record ClassInfo(
string Name,
string Namespace,
ImmutableArray<PropertyInfo> Properties) : IEquatable<ClassInfo>
{
public bool Equals(ClassInfo? other)
{
if (other is null) return false;
if (ReferenceEquals(this, other)) return true;
return Name == other.Name &&
Namespace == other.Namespace &&
Properties.SequenceEqual(other.Properties);
}
public override int GetHashCode()
{
var hash = new HashCode();
hash.Add(Name);
hash.Add(Namespace);
foreach (var prop in Properties)
{
hash.Add(prop);
}
return hash.ToHashCode();
}
}
// 使用 WithComparer
public void Initialize(IncrementalGeneratorInitializationContext context)
{
var classInfos = context.SyntaxProvider
.ForAttributeWithMetadataName(
"GenerateAttribute",
predicate: (node, _) => node is ClassDeclarationSyntax,
transform: (ctx, _) => GetClassInfo(ctx))
.WithComparer(new ClassInfoComparer());
context.RegisterSourceOutput(classInfos, GenerateCode);
}
public class ClassInfoComparer : IEqualityComparer<ClassInfo>
{
public bool Equals(ClassInfo? x, ClassInfo? y)
{
if (ReferenceEquals(x, y)) return true;
if (x is null || y is null) return false;
return x.Equals(y);
}
public int GetHashCode(ClassInfo obj)
{
return obj.GetHashCode();
}
}4. 使用 ValueProvider 缓存昂贵的计算
csharp
public void Initialize(IncrementalGeneratorInitializationContext context)
{
// 缓存编译级别的信息(只计算一次)
var wellKnownTypes = context.CompilationProvider
.Select((compilation, _) => new WellKnownTypes
{
StringType = compilation.GetSpecialType(SpecialType.System_String),
Int32Type = compilation.GetSpecialType(SpecialType.System_Int32),
ObjectType = compilation.GetSpecialType(SpecialType.System_Object),
IEnumerableType = compilation.GetTypeByMetadataName(
"System.Collections.Generic.IEnumerable`1")
});
var classInfos = context.SyntaxProvider
.ForAttributeWithMetadataName(
"GenerateAttribute",
predicate: (node, _) => node is ClassDeclarationSyntax,
transform: (ctx, _) => GetClassInfo(ctx));
// 组合缓存的类型信息和类信息
var combined = wellKnownTypes.Combine(classInfos);
context.RegisterSourceOutput(combined, (spc, data) =>
{
var (types, classInfo) = data;
var code = GenerateCode(classInfo, types);
spc.AddSource($"{classInfo.Name}.g.cs", code);
});
}
public record WellKnownTypes
{
public INamedTypeSymbol StringType { get; init; }
public INamedTypeSymbol Int32Type { get; init; }
public INamedTypeSymbol ObjectType { get; init; }
public INamedTypeSymbol IEnumerableType { get; init; }
}5. 优化字符串操作
✅ 推荐:使用 StringBuilder 和 string interpolation
csharp
public string GenerateCode(ClassInfo classInfo)
{
var sb = new StringBuilder(capacity: 1024); // 预分配容量
sb.AppendLine($"namespace {classInfo.Namespace}");
sb.AppendLine("{");
sb.AppendLine($" public partial class {classInfo.Name}");
sb.AppendLine(" {");
// 使用 string.Join 而不是循环拼接
var properties = string.Join(Environment.NewLine,
classInfo.Properties.Select(p =>
$" public {p.Type} {p.Name} {{ get; set; }}"));
sb.AppendLine(properties);
sb.AppendLine(" }");
sb.AppendLine("}");
return sb.ToString();
}❌ 避免:频繁的字符串拼接
csharp
// ❌ 性能差
public string GenerateCodeSlow(ClassInfo classInfo)
{
string code = "";
code += $"namespace {classInfo.Namespace}\n";
code += "{\n";
foreach (var property in classInfo.Properties)
{
code += $" public {property.Type} {property.Name} {{ get; set; }}\n";
}
code += "}\n";
return code;
}💡 关键要点
- 始终使用增量生成器 - 性能提升 10-100 倍
- 早期过滤 - 在 predicate 中进行语法层面的过滤
- 使用 record 类型 - 支持缓存和值相等性
- 避免不必要的语义分析 - 只在需要时进行
- 使用 StringBuilder - 构建代码时使用 StringBuilder
- 批量处理 - 使用 Collect() 收集数据
- 缓存昂贵的计算 - 使用 CompilationProvider 缓存
- 使用 WithComparer - 为自定义类型提供比较器
🔗 相关资源
📖 下一步
继续学习代码质量最佳实践 → 代码质量最佳实践
最后更新: 2025-01-21文档版本: 1.0