Skip to content

第 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; }
}

这里你能看到两件事:

  1. 类上有一个 [GenerateToString]
  2. 类本身是 partial

但你看不到第三件事:这个类并没有手写 ToString()

源生成器的作用,就是在编译这份代码时,额外补出另一份类似下面的代码:

csharp
public partial class Person
{
    public override string ToString()
    {
        return $"Person {{ Name = {Name}, Age = {Age} }}";
    }
}

也就是说,真正参与编译的代码,不只是你自己手写的文件,还包括生成器在编译阶段塞进去的那部分代码。

这和反射有什么区别

很多新手第一次听到源生成器时,会把它和反射混在一起。最简单的区分方法是看发生时机:

  • 反射:程序运行时再去看类型信息
  • 源生成器:程序运行前,编译时就把代码补好了

所以源生成器更像“提前写好”,而反射更像“运行时再去猜和查”。

为什么第一个案例选 ToString

因为它足够简单,又足够完整:

  • 需要一个特性做入口
  • 需要找到被标记的类
  • 需要读取类和属性信息
  • 需要输出新的 C# 代码

它刚好能把源生成器最重要的主线走一遍。

如何验证

你现在先做 3 个最小动作:

  1. 打开 sample/01-tostring-generator/ToStringGenerator.Sample/Examples/SimpleClass.cs
  2. 确认类上有 [GenerateToString]
  3. 确认这个文件里没有手写 ToString()

如果这三步你都看到了,就说明你已经理解了这个教程的核心出发点:我们要解释的是“为什么没写的方法会在编译后出现”。

如果你还不知道这个文件和整个案例的关系,先补看 源码导读

常见误解

  • 误解 1:源生成器是在程序运行时动态拼代码
    • 不是,它发生在编译时
  • 误解 2:源生成器就是一种模板引擎
    • 不完全是,它和编译器集成,能直接读取语法和语义信息
  • 误解 3:只有特别高级的框架才会用到源生成器
    • 不是,很多日常项目也适合用它减少样板代码

本章新名词

  • Roslyn
  • Source Generator
  • Incremental Generator
  • .g.cs

本章小结

先记住一句最重要的话:源生成器不是运行时动态拼代码,而是编译时补代码。

下一章我们不急着看实现,先让这个案例真正跑起来,亲眼看到它已经生效。

返回主教程目录 | 下一章:运行第一个示例

基于当前仓库文档副本构建的 VitePress 站点