Skip to content

什么是源生成器?

深入理解源生成器的概念、历史和演进

概述

源生成器(Source Generator)是 C# 编译器(Roslyn)的一个强大功能,它允许开发者在编译过程中检查用户代码并生成额外的 C# 源文件。这些生成的文件会自动添加到编译中,成为最终程序集的一部分。

核心概念

源生成器本质上是一个在编译时运行的程序,它可以:

  1. 读取现有代码: 通过 Roslyn API 访问语法树和语义模型
  2. 分析代码结构: 查找特定的模式、特性或代码结构
  3. 生成新代码: 创建新的 C# 源文件并添加到编译中
  4. 报告诊断: 向编译器报告错误、警告或信息

工作时机

源生成器在编译管道的特定阶段运行:

源代码 → 语法分析 → [源生成器运行] → 语义分析 → IL 生成 → 程序集

这意味着生成的代码会经过完整的编译检查,享有与手写代码相同的类型安全保障。

与传统代码生成的区别

传统的代码生成工具(如 T4 模板)通常:

  • 在编译前运行
  • 生成的代码保存为独立文件
  • 需要手动触发或配置构建步骤

而源生成器:

  • 在编译过程中运行
  • 生成的代码直接添加到编译(默认不保存为文件)
  • 自动集成到编译流程,无需额外配置

历史和演进

.NET Framework 时代

在源生成器出现之前,.NET 开发者主要依赖以下技术:

  1. T4 模板 (Text Template Transformation Toolkit)

    • Visual Studio 的代码生成工具
    • 基于文本模板生成代码
    • 需要手动运行或配置自动运行
    • 与 IDE 集成较弱
  2. 反射 (Reflection)

    • 运行时检查和操作类型信息
    • 性能开销大
    • 缺乏编译时类型安全
  3. IL 编织 (IL Weaving)

    • 使用工具(如 Fody)修改编译后的 IL 代码
    • 强大但复杂
    • 调试困难

源生成器的诞生

C# 9.0 / .NET 5 (2020)

  • 首次引入源生成器功能
  • 提供 ISourceGenerator 接口
  • 基于 Roslyn 编译器平台
  • 目标:提供编译时代码生成的标准方式

主要动机

  1. 提高性能:替代运行时反射
  2. 改善开发体验:更好的 IDE 集成
  3. 类型安全:编译时检查生成的代码
  4. 简化工具链:统一的代码生成方式

增量生成器的引入

C# 10 / .NET 6 (2021)

  • 引入 IIncrementalGenerator 接口
  • 解决性能问题:传统生成器在大型项目中可能很慢
  • 引入管道模式和缓存机制
  • 支持增量编译:只在相关代码变化时重新生成

性能改进

  • 传统生成器:每次编译都完整运行
  • 增量生成器:利用缓存,只处理变化的部分
  • 在大型项目中,性能提升可达 10-100 倍

持续演进

C# 11 / .NET 7 (2022)

  • 引入 ForAttributeWithMetadataName API
  • 专门优化基于特性的生成器
  • 进一步提升性能

C# 12 / .NET 8 (2023)

  • 改进诊断 API
  • 更好的错误报告
  • 增强的 IDE 支持

C# 13 / .NET 9-10 (2024-2025)

  • 引入 AddEmbeddedAttributeDefinition API
  • 解决 "marker attribute" 的类型冲突问题
  • 简化特性定义的生成

行业采用

源生成器已被广泛采用:

  1. Microsoft 官方库

    • System.Text.Json (JSON 序列化)
    • Microsoft.Extensions.Logging (高性能日志)
    • ASP.NET Core (最小 API)
    • gRPC
  2. 社区项目

    • Dapper (ORM)
    • MediatR (CQRS)
    • AutoMapper
    • 各种 MVVM 框架

与其他代码生成技术的对比

源生成器 vs T4 模板

特性源生成器T4 模板
运行时机编译时(集成)编译前(独立)
生成的代码内存中(可选保存)保存为文件
IDE 集成优秀(实时反馈)一般(需要手动运行)
类型安全强(编译检查)弱(模板语法)
调试容易(可查看生成的代码)困难(模板调试复杂)
性能影响小(增量生成)中(每次都生成文件)
学习曲线陡(需要 Roslyn API)中(模板语法)
跨平台完全支持有限(依赖 Visual Studio)

何时使用 T4

  • 需要生成非 C# 代码(如 SQL、XML)
  • 简单的文本转换
  • 已有大量 T4 模板的遗留项目

何时使用源生成器

  • 生成 C# 代码
  • 需要分析现有代码
  • 性能关键的场景
  • 新项目或现代化改造

源生成器 vs 反射

特性源生成器反射
执行时机编译时运行时
性能快(零运行时开销)慢(运行时查询)
类型安全强(编译检查)弱(字符串查询)
代码可见性可见(可查看生成的代码)不可见(动态执行)
AOT 兼容性完全兼容有限(需要特殊处理)
调试容易困难
灵活性中(编译时确定)高(运行时动态)

性能对比示例

csharp
// 反射方式(运行时)
public object GetPropertyValue(object obj, string propertyName)
{
    var property = obj.GetType().GetProperty(propertyName);
    return property?.GetValue(obj);
}
// 性能:~500 ns

// 源生成器方式(编译时生成)
public object GetPropertyValue(Person person, string propertyName)
{
    return propertyName switch
    {
        "Name" => person.Name,
        "Age" => person.Age,
        _ => null
    };
}
// 性能:~5 ns
// 提升:100x

何时使用反射

  • 需要运行时动态性
  • 处理未知类型
  • 插件系统
  • 简单的场景(性能不关键)

何时使用源生成器

  • 性能关键的场景
  • 类型在编译时已知
  • 需要 AOT 编译
  • 大量重复的代码模式

源生成器 vs IL 编织 (IL Weaving)

特性源生成器IL 编织
修改现有代码不能
添加新代码
复杂度
调试容易困难
工具依赖无(内置)需要(如 Fody)
透明度高(可见生成的代码)低(修改 IL)
维护性一般

何时使用 IL 编织

  • 必须修改现有代码
  • 需要注入横切关注点(AOP)
  • 已有基于 IL 编织的解决方案

何时使用源生成器

  • 只需要添加新代码
  • 希望代码透明可见
  • 追求更好的可维护性
  • 新项目

源生成器 vs 代码片段 (Code Snippets)

特性源生成器代码片段
自动化程度完全自动手动触发
代码同步自动更新手动更新
复杂度可处理复杂逻辑简单模板
适用场景重复模式快速输入

何时使用代码片段

  • 快速输入常用代码
  • 简单的代码模板
  • 个人开发习惯

何时使用源生成器

  • 需要根据现有代码生成
  • 复杂的代码生成逻辑
  • 团队共享的代码模式

总结

源生成器是 .NET 生态系统中代码生成技术的重要演进,它结合了:

  • 编译时执行:零运行时开销
  • 类型安全:完整的编译检查
  • IDE 集成:实时反馈和智能提示
  • 性能优化:增量生成和缓存
  • 易于调试:可查看生成的代码

虽然学习曲线较陡,但对于需要大量重复代码或性能关键的场景,源生成器是最佳选择。随着 .NET 的持续演进,源生成器的功能和性能还在不断改进。


下一步: 阅读 Roslyn 编译器平台详解 了解源生成器的技术基础。

基于 MIT 许可发布