最佳实践完整参考
本文档总结源生成器开发的最佳实践,包括架构设计、性能优化、代码质量和测试策略。
📋 文档信息
| 属性 | 值 |
|---|---|
| 难度 | 中级 - 高级 |
| 阅读时间 | 10 分钟 |
| 前置知识 | 源生成器基础、C# 编程 |
🎯 学习目标
学完本系列文档后,你将能够:
- ✅ 设计模块化、可维护的生成器架构
- ✅ 优化生成器性能,提升编译速度
- ✅ 编写高质量、可测试的生成器代码
- ✅ 实施完整的测试策略
- ✅ 遵循行业最佳实践
📚 快速导航
核心主题
| 主题 | 描述 | 链接 |
|---|---|---|
| 架构设计 | 模块化设计、关注点分离、依赖注入 | 查看详情 |
| 性能优化 | 增量生成、早期过滤、缓存策略 | 查看详情 |
| 代码质量 | 文档注释、错误处理、代码格式化 | 查看详情 |
| 测试策略 | 单元测试、快照测试、性能测试 | 查看详情 |
🎓 最佳实践概览
最佳实践分类
开发流程
💡 核心原则
1. 始终使用增量生成器
增量生成器(IIncrementalGenerator)是现代源生成器的标准:
- 性能提升: 10-100 倍性能提升
- 自动缓存: 只在输入改变时重新生成
- IDE 集成: 更好的开发体验
- 未来标准: 推荐的实现方式
2. 模块化设计
将生成器拆分为独立的模块:
- 数据收集器: 从语法树收集信息
- 代码生成器: 生成目标代码
- 工具类: 共享的辅助功能
- 常量配置: 集中管理配置
3. 早期过滤
在语法层面尽早过滤不需要的节点:
- predicate: 使用语法信息快速过滤
- transform: 只对需要的节点进行语义分析
- 性能优化: 避免不必要的计算
4. 完善的错误处理
使用诊断而不是异常:
- 捕获异常: 不要让异常传播出生成器
- 报告诊断: 使用
ReportDiagnostic报告错误 - 用户友好: 提供清晰的错误信息
5. 编写测试
为生成器编写完整的测试:
- 单元测试: 测试单个功能
- 快照测试: 验证生成的代码
- 集成测试: 测试完整流程
- 性能测试: 测量执行时间
📊 最佳实践检查清单
架构设计
- [ ] 使用模块化设计
- [ ] 分离关注点
- [ ] 使用工厂模式
- [ ] 支持依赖注入
性能优化
- [ ] 使用增量生成器
- [ ] 早期过滤
- [ ] 使用 record 类型
- [ ] 避免不必要的语义分析
- [ ] 使用 StringBuilder
代码质量
- [ ] 完整的 XML 文档注释
- [ ] 使用常量和配置
- [ ] 完善的错误处理
- [ ] 生成格式良好的代码
测试
- [ ] 编写单元测试
- [ ] 使用快照测试
- [ ] 集成测试
- [ ] 性能测试
可维护性
- [ ] 清晰的命名约定
- [ ] 适当的注释
- [ ] 版本兼容性处理
- [ ] 文档完善
❓ 常见问题解答 (FAQ)
Q1: 什么时候应该使用增量生成器而不是 ISourceGenerator?
A: 几乎总是应该使用增量生成器(IIncrementalGenerator):
- 性能: 增量生成器支持缓存,只在输入改变时重新生成
- 可扩展性: 更适合大型项目
- 现代化: 这是推荐的新方式
只有在以下情况下才考虑使用 ISourceGenerator:
- 需要支持旧版本的 Roslyn(< 4.0)
- 生成器非常简单,性能不是问题
Q2: 如何处理生成器中的异常?
A: 永远不要让异常传播出生成器:
csharp
public void Execute(GeneratorExecutionContext context)
{
try
{
// 生成代码
var code = GenerateCode();
context.AddSource("Generated.g.cs", code);
}
catch (Exception ex)
{
// 报告诊断而不是抛出异常
context.ReportDiagnostic(Diagnostic.Create(
new DiagnosticDescriptor(
"GEN001",
"Generation Error",
$"Failed to generate code: {ex.Message}",
"Generator",
DiagnosticSeverity.Error,
true),
Location.None));
}
}Q3: 生成器可以访问外部文件吗?
A: 可以,但有限制:
csharp
// ✅ 推荐:使用 AdditionalFiles
public void Initialize(IncrementalGeneratorInitializationContext context)
{
var additionalFiles = context.AdditionalTextsProvider
.Where(file => file.Path.EndsWith(".template"))
.Select((file, ct) => file.GetText(ct)?.ToString());
context.RegisterSourceOutput(additionalFiles, (spc, content) =>
{
// 使用文件内容
});
}
// ❌ 避免:直接读取文件系统
// File.ReadAllText("template.txt"); // 不推荐!配置 AdditionalFiles:
xml
<ItemGroup>
<AdditionalFiles Include="Templates\*.template" />
</ItemGroup>Q4: 如何在生成器之间共享代码?
A: 使用共享项目或 NuGet 包:
xml
<!-- 方法 1: 共享项目 -->
<ItemGroup>
<ProjectReference Include="..\Shared\Shared.csproj" />
</ItemGroup>
<!-- 方法 2: NuGet 包 -->
<ItemGroup>
<PackageReference Include="MySharedLibrary" Version="1.0.0" PrivateAssets="all" />
</ItemGroup>注意:
- 共享的代码必须兼容 .NET Standard 2.0
- 使用
PrivateAssets="all"避免依赖传递
Q5: 如何测试生成器的性能?
A: 使用 BenchmarkDotNet 进行性能测试:
csharp
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
[MemoryDiagnoser]
public class GeneratorBenchmarks
{
private Compilation _compilation;
private MyGenerator _generator;
[GlobalSetup]
public void Setup()
{
_compilation = CreateCompilation();
_generator = new MyGenerator();
}
[Benchmark]
public void RunGenerator()
{
var driver = CSharpGeneratorDriver.Create(_generator);
driver.RunGenerators(_compilation);
}
}🔗 相关资源
API 文档
指南
示例
📖 下一步
- 架构设计: 学习如何设计模块化的生成器 → 架构设计最佳实践
- 性能优化: 了解性能优化技巧 → 性能优化最佳实践
- 代码质量: 提升代码质量 → 代码质量最佳实践
- 测试策略: 实施完整的测试 → 测试最佳实践
最后更新: 2025-01-21文档版本: 3.0