Skip to content

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

你可以先把这段代码粗略理解成三层:

  1. ForAttributeWithMetadataName(...)
    • 按特性名找目标
  2. predicate: IsClassDeclaration
    • 进一步限制:这里只关心类声明
  3. transform: TransformClass
    • 把找到的目标转成我们真正想要的数据模型

为什么特性特别适合作为入口

特性最大的优点是“显式”。

也就是说:

  • 用户主动在类上写了 [GenerateToString]
  • 生成器就知道“这个类是你想让我处理的”

相比“扫描所有类再靠命名约定猜测”,这种方式更明确,也更不容易误伤无关代码。

fullyQualifiedMetadataName 为什么要写全名

这里写的是:

csharp
"ToStringGenerator.GenerateToStringAttribute"

这不是随便写的,而是为了避免重名歧义。因为项目里可能出现多个名字相似的特性,只有完全限定名才能明确告诉编译器:到底要找哪一个。

如果以后要读取特性参数,看哪里

当前主案例的 [GenerateToString] 没有参数,所以你现在还看不到这一步。但后面自己写生成器时,通常很快会遇到这种写法:

csharp
[GenerateToString(IncludePrivate = true)]

这时你要先记住一条最短路径:

  • transform 回调里拿到的是 GeneratorAttributeSyntaxContext
  • 其中的 context.Attributes 是匹配到的特性集合
  • 集合里的每一项都是 AttributeData
  • 继续往下看:
    • ConstructorArguments
      • 读取位置参数
    • NamedArguments
      • 读取命名参数

也就是说,当前这一章先解决“怎么找到被特性标记的类”,而“怎么读特性参数”则是这条链路的下一站。

如何验证

按下面顺序做:

  1. 打开 sample/01-tostring-generator/ToStringGenerator.Sample/Examples/SimpleClass.cs
  2. 再打开 ComplexClass.csGenericClass.csNestedClass.cs
  3. 确认这些类型都带有 [GenerateToString]
  4. 回到 ToStringGenerator.cs,确认 fullyQualifiedMetadataName 指向的是生成的特性全名
  5. 找到 IsClassDeclaration(...),确认它只允许类声明通过

完成后你应该能说清:生成器不是“到处乱找”,而是先由特性把目标范围缩小了。

常见误解

  • 误解 1:特性只是语法糖,不影响生成逻辑
    • 在这里它就是生成器的入口开关
  • 误解 2:只要看到 [GenerateToString] 文本就够了
    • 还不够,生成器要通过编译器提供的筛选 API 精准处理它
  • 误解 3:ForAttributeWithMetadataName(...) 只是“找特性”的简单工具
    • 它实际承担的是“按特性高效筛选 + 进入后续转换”的关键角色

本章新名词

  • Attribute
  • SyntaxProvider
  • ForAttributeWithMetadataName(...)
  • GeneratorAttributeSyntaxContext
  • AttributeData

本章小结

这一章最重要的收获是:你第一次看到了“生成器如何锁定目标”的机制。固定输出解决的是“怎么加代码”,而特性筛选解决的是“给谁加代码”。

下一章我们要补上另一个关键认知:为什么只看类声明还不够,为什么必须进入语义世界。

上一章 | 返回主教程目录 | 下一章:语法与语义

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