Appearance
第 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 步:
RegisterPostInitializationOutput(...)- 注册一段固定输出
attributeSource- 准备一段 C# 源码字符串
AddSource(...)- 把这段源码加入当前编译
为什么这里适合生成特性定义
因为这个特性本身是固定的:
- 不管用户项目里有几个类
- 不管类里有多少属性
- 不管有没有泛型和嵌套类
这个特性定义本身都不会变。
所以它特别适合作为“固定输出”的第一个例子。
如何验证
按下面顺序做:
- 在
ToStringGenerator.cs中找到GenerateAttribute(...) - 确认
Initialize(...)调用了它 - 构建或运行示例工程
- 去
sample/01-tostring-generator/ToStringGenerator.Sample/obj/Debug/net8.0/generated找GenerateToStringAttribute.g.cs - 打开这个生成文件,确认里面真的是特性定义
动手练习
- 临时把
GenerateToStringAttribute改成别的名字,再重新构建,观察示例工程哪里开始报错 - 再加一个固定输出文件,例如一个空的
GeneratedConstants类 - 改一下
hintName,观察生成文件名变化
常见误解
- 误解 1:固定输出很简单,所以没有学习价值
- 恰恰相反,它是理解
AddSource(...)的最短路径
- 恰恰相反,它是理解
- 误解 2:
AddSource(...)一定会在项目目录里写出物理文件- 更准确的说法是:它把源码加入编译输入
- 误解 3:生成特性只是个小技巧,不重要
- 它实际上是后续“特性驱动生成”整条链路的入口
本章新名词
RegisterPostInitializationOutput(...)AddSource(...)hintNameSourceText
本章小结
到这里你已经掌握了最短路径:注册固定输出、准备源码、加入编译。
里程碑 1:你已经能生成固定代码
你现在应该具备的能力
- 能说清楚源生成器发生在编译时
- 能跑通
sample/01-tostring-generator - 能解释三个工程的分工
- 能看懂最小增量生成器骨架
- 能理解
RegisterPostInitializationOutput(...)+AddSource(...)
自检问题
- 为什么示例项目没有手写
ToString()也能输出结果? - 为什么固定输出适合作为第一个练习?
hintName是真实源文件路径吗?
下一章开始,我们才正式进入“根据用户代码生成不同结果”的阶段:先学会从特性找到目标类。
上一章 | 返回主教程目录 | 下一章:从特性到目标类