Appearance
第 1 章:源生成器是什么
本章目标
- 知道源生成器要解决的真实问题
- 理解“编译时生成代码”到底是什么意思
- 对本教程使用的主案例建立第一层直觉
如何使用本教程
- 完全零基础读者:建议按第 1 章一路顺着读到第 12 章
- 已经会一点 C#:也尽量不要跳过第 1-5 章,因为那几章负责建立“编译时补代码”的直觉
- 遇到对象、方法、属性看不懂时:立刻切到 术语与 API 手册
- 卡在生成结果、路径或报错时:优先查看 常见问题与排错
先看现象
很多项目里都有这样一类代码:
- 写起来不难
- 规则很固定
- 重复率很高
- 一旦模型变化,就要跟着改很多地方
典型例子包括:
ToString()- Builder
- DTO 映射
- 依赖注入注册代码
- 序列化或反序列化辅助代码
这些代码最麻烦的地方,不是不会写,而是“每次都要写”。
再看代码
这里开始,文档里的源码路径都按“仓库根目录相对路径”来写。
在主案例里,用户类是这样写的:
sample/01-tostring-generator/ToStringGenerator.Sample/Examples/SimpleClass.cs
csharp
[GenerateToString]
public partial class Person
{
public string Name { get; set; } = string.Empty;
public int Age { get; set; }
}这里你能看到两件事:
- 类上有一个
[GenerateToString] - 类本身是
partial
但你看不到第三件事:这个类并没有手写 ToString()。
源生成器的作用,就是在编译这份代码时,额外补出另一份类似下面的代码:
csharp
public partial class Person
{
public override string ToString()
{
return $"Person {{ Name = {Name}, Age = {Age} }}";
}
}也就是说,真正参与编译的代码,不只是你自己手写的文件,还包括生成器在编译阶段塞进去的那部分代码。
这和反射有什么区别
很多新手第一次听到源生成器时,会把它和反射混在一起。最简单的区分方法是看发生时机:
- 反射:程序运行时再去看类型信息
- 源生成器:程序运行前,编译时就把代码补好了
所以源生成器更像“提前写好”,而反射更像“运行时再去猜和查”。
为什么第一个案例选 ToString
因为它足够简单,又足够完整:
- 需要一个特性做入口
- 需要找到被标记的类
- 需要读取类和属性信息
- 需要输出新的 C# 代码
它刚好能把源生成器最重要的主线走一遍。
如何验证
你现在先做 3 个最小动作:
- 打开
sample/01-tostring-generator/ToStringGenerator.Sample/Examples/SimpleClass.cs - 确认类上有
[GenerateToString] - 确认这个文件里没有手写
ToString()
如果这三步你都看到了,就说明你已经理解了这个教程的核心出发点:我们要解释的是“为什么没写的方法会在编译后出现”。
如果你还不知道这个文件和整个案例的关系,先补看 源码导读。
常见误解
- 误解 1:源生成器是在程序运行时动态拼代码
- 不是,它发生在编译时
- 误解 2:源生成器就是一种模板引擎
- 不完全是,它和编译器集成,能直接读取语法和语义信息
- 误解 3:只有特别高级的框架才会用到源生成器
- 不是,很多日常项目也适合用它减少样板代码
本章新名词
- Roslyn
- Source Generator
- Incremental Generator
.g.cs
本章小结
先记住一句最重要的话:源生成器不是运行时动态拼代码,而是编译时补代码。
下一章我们不急着看实现,先让这个案例真正跑起来,亲眼看到它已经生效。