Skip to content

最佳实践完整参考

本文档总结源生成器开发的最佳实践,包括架构设计、性能优化、代码质量和测试策略。


📋 文档信息

属性
难度中级 - 高级
阅读时间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 文档

指南

示例


📖 下一步

  1. 架构设计: 学习如何设计模块化的生成器 → 架构设计最佳实践
  2. 性能优化: 了解性能优化技巧 → 性能优化最佳实践
  3. 代码质量: 提升代码质量 → 代码质量最佳实践
  4. 测试策略: 实施完整的测试 → 测试最佳实践

最后更新: 2025-01-21文档版本: 3.0

基于 MIT 许可发布