Appearance
第 6 章:从特性到目标类
本章目标
- 理解为什么特性是源生成器最常见的入口
- 看懂生成器如何从
[GenerateToString]找到目标类 - 认识
ForAttributeWithMetadataName(...)在整条链路中的位置
先看现象
用户代码只是多写了一行:
csharp
[GenerateToString]但生成器却能做到两件事:
- 只处理被标记的类
- 不去处理没有标记的类
这说明生成器并不是“扫描所有类再一个个猜”,而是有一条更精准的筛选路径。
再看代码
主案例里的关键逻辑在:
sample/01-tostring-generator/ToStringGenerator/ToStringGenerator.cs
csharp
var classDeclarations = context.SyntaxProvider
.ForAttributeWithMetadataName(
fullyQualifiedMetadataName: "ToStringGenerator.GenerateToStringAttribute",
predicate: IsClassDeclaration,
transform: TransformClass)
.Where(classInfo => classInfo is not null);你可以先把这段代码粗略理解成三层:
ForAttributeWithMetadataName(...)- 按特性名找目标
predicate: IsClassDeclaration- 进一步限制:这里只关心类声明
transform: TransformClass- 把找到的目标转成我们真正想要的数据模型
为什么特性特别适合作为入口
特性最大的优点是“显式”。
也就是说:
- 用户主动在类上写了
[GenerateToString] - 生成器就知道“这个类是你想让我处理的”
相比“扫描所有类再靠命名约定猜测”,这种方式更明确,也更不容易误伤无关代码。
fullyQualifiedMetadataName 为什么要写全名
这里写的是:
csharp
"ToStringGenerator.GenerateToStringAttribute"这不是随便写的,而是为了避免重名歧义。因为项目里可能出现多个名字相似的特性,只有完全限定名才能明确告诉编译器:到底要找哪一个。
如果以后要读取特性参数,看哪里
当前主案例的 [GenerateToString] 没有参数,所以你现在还看不到这一步。但后面自己写生成器时,通常很快会遇到这种写法:
csharp
[GenerateToString(IncludePrivate = true)]这时你要先记住一条最短路径:
transform回调里拿到的是GeneratorAttributeSyntaxContext- 其中的
context.Attributes是匹配到的特性集合 - 集合里的每一项都是
AttributeData - 继续往下看:
ConstructorArguments- 读取位置参数
NamedArguments- 读取命名参数
也就是说,当前这一章先解决“怎么找到被特性标记的类”,而“怎么读特性参数”则是这条链路的下一站。
如何验证
按下面顺序做:
- 打开
sample/01-tostring-generator/ToStringGenerator.Sample/Examples/SimpleClass.cs - 再打开
ComplexClass.cs、GenericClass.cs、NestedClass.cs - 确认这些类型都带有
[GenerateToString] - 回到
ToStringGenerator.cs,确认fullyQualifiedMetadataName指向的是生成的特性全名 - 找到
IsClassDeclaration(...),确认它只允许类声明通过
完成后你应该能说清:生成器不是“到处乱找”,而是先由特性把目标范围缩小了。
常见误解
- 误解 1:特性只是语法糖,不影响生成逻辑
- 在这里它就是生成器的入口开关
- 误解 2:只要看到
[GenerateToString]文本就够了- 还不够,生成器要通过编译器提供的筛选 API 精准处理它
- 误解 3:
ForAttributeWithMetadataName(...)只是“找特性”的简单工具- 它实际承担的是“按特性高效筛选 + 进入后续转换”的关键角色
本章新名词
AttributeSyntaxProviderForAttributeWithMetadataName(...)GeneratorAttributeSyntaxContextAttributeData
本章小结
这一章最重要的收获是:你第一次看到了“生成器如何锁定目标”的机制。固定输出解决的是“怎么加代码”,而特性筛选解决的是“给谁加代码”。
下一章我们要补上另一个关键认知:为什么只看类声明还不够,为什么必须进入语义世界。