Appearance
第 9 章:先生成一个简单方法
本章目标
- 在进入完整
ToString()之前,先理解动态生成的最小版本 - 理解“教学中间态”和“最终实现”的关系
- 把“读取信息”这一步真正接到“输出代码”这一步上
本章在主线里的位置
- 对应 Level 1:简单动态生成
- 这一章的重点不是完整功能,而是先看懂“输入变化,输出也跟着变化”
先看现象
如果你一上来就直接读完整 ToString() 的生成逻辑,很容易被这些细节打断:
- null 处理
- 值类型和引用类型差异
- 泛型参数
- 泛型约束
- 嵌套类结构
所以这一章先做一个“中间态”:
csharp
public partial class Person
{
public string GetGeneratedTypeName() => "Person";
}这个例子比完整 ToString() 简单得多,但它已经完整包含了动态生成最核心的一步:
- 先拿到类信息
- 再根据类信息生成不同的代码
再看代码
主案例里真正的输出阶段在:
sample/01-tostring-generator/ToStringGenerator/ToStringGenerator.cs
关键连接点是:
csharp
context.RegisterSourceOutput(classDeclarations, (spc, classInfo) =>
{
if (classInfo is not null)
{
GenerateToStringCode(spc, classInfo);
}
});你可以先把它拆开理解:
classDeclarations- 前面筛出来并转换好的目标数据
spc- 输出上下文,也就是后面真正调用
AddSource(...)的地方
- 输出上下文,也就是后面真正调用
classInfo- 当前要处理的那一个类的信息
为什么这一章要先用“简单方法”做中间态
因为你现在已经会:
- 找目标类
- 拿类信息
但如果此时直接进入完整 ToString(),你会同时处理太多维度。用 GetGeneratedTypeName() 这种简化版做过渡,可以把注意力集中在一件事上:
- “拿到数据之后,怎么把它变成一段新的源码”
动态生成和固定输出的本质差别
前一阶段的固定输出是:
- 不管输入是什么
- 输出基本都一样
这一章的动态输出是:
- 输入不同
- 输出也要跟着不同
比如:
Person生成GetGeneratedTypeName() => "Person"Employee生成GetGeneratedTypeName() => "Employee"
如何验证
按下面顺序做:
- 回到
RegisterSourceOutput(...)那段代码,确认classInfo是怎么流入输出阶段的 - 试着只使用
classInfo.ClassName,在脑中构造一份最小生成代码 - 对比固定输出章节想一想:现在多出来的那份输入是什么
- 再回头看完整案例,理解这一章的中间态只是教学过渡,最终实现仍然会回到真实的
ToString()
动手练习
- 自己写一版最小的
GetGeneratedTypeName()代码草稿 - 用一句话解释:为什么这个中间态不需要属性列表
- 再用一句话解释:从固定输出到动态输出,最关键新增的输入是什么
常见误解
- 误解 1:教学中间态不是仓库最终代码,所以没必要看
- 恰恰相反,它是帮助你平滑跨过“第一次动态输出”这道坎的
- 误解 2:只要会
AddSource(...)就已经掌握动态生成- 还不够,关键是你要知道“根据什么数据生成不同输出”
- 误解 3:
RegisterSourceOutput(...)只是个包裹函数- 它实际上是把前面的分析阶段和后面的输出阶段连接起来的关键点
本章新名词
RegisterSourceOutput(...)SourceProductionContext- 动态输出
- 教学中间态
本章小结
你已经跨过最重要的一步:从“拿到信息”走到了“根据信息生成不同代码”。
下一章开始,我们把这条动态生成链路加上真实世界里的复杂度:null、泛型、嵌套类和类型差异。