第 2 步:理解源生成器
加载进度中...
📋 本步目标
完成本步后,你将能够:
- 理解什么是源生成器
- 了解源生成器的应用场景
- 掌握源生成器的工作原理
- 理解源生成器与反射的区别
⏱️ 预计时间
约 30 分钟
📚 学习内容
1. 什么是源生成器?
源生成器(Source Generator) 是 C# 编译器(Roslyn)的一个强大功能,它允许开发者在编译时检查代码并生成额外的 C# 源文件。
核心特点
- ✅ 编译时执行 - 在编译过程中运行,不影响运行时性能
- ✅ 类型安全 - 生成的代码在编译时检查,确保类型安全
- ✅ 零运行时开销 - 不使用反射,性能优异
- ✅ IDE 支持 - 生成的代码可以被 IntelliSense 识别
- ✅ 可调试 - 生成的代码可以像普通代码一样调试
简单类比
想象你有一个助手,在你编译代码时:
- 📖 读取你写的代码
- 🔍 分析代码结构
- ✍️ 自动生成需要的样板代码
- 📦 将生成的代码加入编译
这就是源生成器!
2. 为什么需要源生成器?
传统方式的问题
问题 1:手动编写样板代码
csharp
// 每个类都要手动写 ToString
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
// 😫 手动编写,容易出错
public override string ToString()
{
return $"Person {{ Name = {Name}, Age = {Age} }}";
}
}
// 添加新属性时容易忘记更新 ToString
// 每个类都要重复写类似代码问题 2:使用反射的性能开销
csharp
// 使用反射获取属性值
public string ToStringUsingReflection()
{
var properties = GetType().GetProperties();
var values = properties.Select(p =>
$"{p.Name} = {p.GetValue(this)}");
return $"{GetType().Name} {{ {string.Join(", ", values)} }}";
}
// ❌ 问题:
// - 运行时开销大
// - 类型不安全
// - 难以调试源生成器的解决方案
csharp
// 只需要添加特性
[GenerateToString]
public partial class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
// ✅ 源生成器自动生成:
// - 编译时生成代码
// - 零运行时开销
// - 类型安全
// - IDE 可以识别3. 源生成器的工作原理
源生成器在编译流程中的位置:
详细步骤
编译器解析代码
- 将源代码解析为语法树(Syntax Tree)
- 构建语义模型(Semantic Model)
调用源生成器
- 编译器将语法树和语义模型传递给生成器
- 生成器可以读取和分析代码
生成新代码
- 生成器创建新的 C# 代码
- 返回给编译器
编译所有代码
- 编译器将原始代码和生成的代码一起编译
- 生成最终的程序集
4. 应用场景
源生成器适用于以下场景:
场景 1:自动生成样板代码
csharp
// ToString 生成器
[GenerateToString]
public partial class User { }
// Equals/GetHashCode 生成器
[GenerateEquality]
public partial class Product { }
// Builder 模式生成器
[GenerateBuilder]
public partial class Configuration { }场景 2:序列化/反序列化
csharp
// JSON 序列化器
[JsonSerializable]
public partial class ApiResponse { }
// 二进制序列化器
[BinarySerializable]
public partial class CacheData { }场景 3:依赖注入
csharp
// 自动注册服务
[AutoRegister(ServiceLifetime.Scoped)]
public class UserService : IUserService { }场景 4:ORM 映射
csharp
// 数据库映射
[Table("Users")]
public partial class User
{
[Column("user_id")]
public int Id { get; set; }
}场景 5:代码验证
csharp
// 编译时验证
[ValidateNotNull]
public partial class Config
{
public string ConnectionString { get; set; }
}5. 源生成器 vs 反射
| 特性 | 源生成器 | 反射 |
|---|---|---|
| 执行时机 | 编译时 | 运行时 |
| 性能 | 零开销 | 有开销 |
| 类型安全 | 编译时检查 | 运行时检查 |
| IDE 支持 | 完全支持 | 有限支持 |
| 调试 | 容易 | 困难 |
| 代码可见 | 可见 | 不可见 |
性能对比示例
csharp
// 反射方式(慢)
var value = typeof(Person)
.GetProperty("Name")
.GetValue(person);
// 源生成器方式(快)
var value = person.Name; // 直接访问✅ 检查点
✅ 检查点
完成以下任务后,可以进入下一步:
🎯 小测验
测试一下你的理解:
源生成器在什么时候运行?
查看答案
编译时(Compile Time)。源生成器在编译过程中运行,生成的代码会被一起编译到最终的程序集中。源生成器相比反射有什么优势?
查看答案
主要优势:
- 零运行时开销(编译时生成)
- 类型安全(编译时检查)
- IDE 支持更好
- 更容易调试
- 生成的代码可见
列举 3 个源生成器的应用场景
查看答案
常见场景:
- 自动生成样板代码(ToString、Equals 等)
- 序列化/反序列化
- 依赖注入自动注册
- ORM 数据库映射
- 代码验证
💡 常见问题
Q: 源生成器会增加编译时间吗?
A: 会有一些影响,但通常很小。源生成器在编译时运行,但现代编译器有增量编译优化,只有相关代码改变时才会重新运行生成器。
Q: 生成的代码存储在哪里?
A: 生成的代码存储在编译输出目录的临时文件夹中(通常是 obj 目录)。你可以在 IDE 中查看生成的代码,但不应该手动修改它们。
Q: 源生成器可以修改现有代码吗?
A: 不可以。源生成器只能添加新代码,不能修改或删除现有代码。这是一个重要的设计限制,确保了代码的可预测性。
Q: 什么时候应该使用源生成器?
A: 当你需要:
- 自动生成重复的样板代码
- 避免反射的性能开销
- 在编译时进行代码验证
- 基于代码结构生成相关代码
不适合用于复杂的业务逻辑或需要运行时动态性的场景。
📖 延伸阅读
⏭️ 下一步
现在你已经理解了源生成器的概念和原理,下一步我们将动手创建第一个 Hello World 源生成器!