Skip to content

第 5 章:固定输出 - 生成特性定义

本章目标

  • 学会最简单的生成动作
  • 理解 RegisterPostInitializationOutput(...)AddSource(...)
  • 先做到不依赖用户代码也能生成一份源码

本章在主线里的位置

  • 对应 Level 0:固定输出
  • 这一章先只处理“生成一段固定源码”,不引入目标类筛选和属性读取

先看现象

主案例里第一个真正发生的生成动作,不是生成 ToString(),而是先生成一个特性:

  • GenerateToStringAttribute

这是非常适合零基础读者的第一步,因为它不依赖:

  • 目标类筛选
  • 属性读取
  • 泛型
  • null 处理
  • 嵌套类

也就是说,你可以先只理解“生成器是怎么把一段固定代码加进编译里的”。

再看代码

相关方法在:

  • sample/01-tostring-generator/ToStringGenerator/ToStringGenerator.cs
csharp
private static void GenerateAttribute(IncrementalGeneratorInitializationContext context)
{
    context.RegisterPostInitializationOutput(ctx =>
    {
        var attributeSource = """
            #nullable enable

            namespace ToStringGenerator
            {
                [System.AttributeUsage(System.AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
                public sealed class GenerateToStringAttribute : System.Attribute
                {
                }
            }
            """;

        ctx.AddSource("GenerateToStringAttribute.g.cs", attributeSource);
    });
}

你可以把它拆成 3 步:

  1. RegisterPostInitializationOutput(...)
    • 注册一段固定输出
  2. attributeSource
    • 准备一段 C# 源码字符串
  3. AddSource(...)
    • 把这段源码加入当前编译

为什么这里适合生成特性定义

因为这个特性本身是固定的:

  • 不管用户项目里有几个类
  • 不管类里有多少属性
  • 不管有没有泛型和嵌套类

这个特性定义本身都不会变。

所以它特别适合作为“固定输出”的第一个例子。

如何验证

按下面顺序做:

  1. ToStringGenerator.cs 中找到 GenerateAttribute(...)
  2. 确认 Initialize(...) 调用了它
  3. 构建或运行示例工程
  4. sample/01-tostring-generator/ToStringGenerator.Sample/obj/Debug/net8.0/generatedGenerateToStringAttribute.g.cs
  5. 打开这个生成文件,确认里面真的是特性定义

动手练习

  1. 临时把 GenerateToStringAttribute 改成别的名字,再重新构建,观察示例工程哪里开始报错
  2. 再加一个固定输出文件,例如一个空的 GeneratedConstants
  3. 改一下 hintName,观察生成文件名变化

常见误解

  • 误解 1:固定输出很简单,所以没有学习价值
    • 恰恰相反,它是理解 AddSource(...) 的最短路径
  • 误解 2:AddSource(...) 一定会在项目目录里写出物理文件
    • 更准确的说法是:它把源码加入编译输入
  • 误解 3:生成特性只是个小技巧,不重要
    • 它实际上是后续“特性驱动生成”整条链路的入口

本章新名词

  • RegisterPostInitializationOutput(...)
  • AddSource(...)
  • hintName
  • SourceText

本章小结

到这里你已经掌握了最短路径:注册固定输出、准备源码、加入编译。

里程碑 1:你已经能生成固定代码

你现在应该具备的能力

  • 能说清楚源生成器发生在编译时
  • 能跑通 sample/01-tostring-generator
  • 能解释三个工程的分工
  • 能看懂最小增量生成器骨架
  • 能理解 RegisterPostInitializationOutput(...) + AddSource(...)

自检问题

  1. 为什么示例项目没有手写 ToString() 也能输出结果?
  2. 为什么固定输出适合作为第一个练习?
  3. hintName 是真实源文件路径吗?

下一章开始,我们才正式进入“根据用户代码生成不同结果”的阶段:先学会从特性找到目标类。

上一章 | 返回主教程目录 | 下一章:从特性到目标类

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