前言
接前文,这节主要写写 C# 在编译器体系中的结构。
在 Roslyn 中,常用 syntax node 和 symbol 两大模型来表达代码。
Syntax Node
Syntax node(语法节点)是 C# syntax tree(语法树)的基本成分,一个类(ClassDeclarationSyntax)、一个接口(InterfaceDeclarationSyntax)、一个 using 语句(UsingDirectiveSyntax)、一个命名空间声明(NamespaceDeclarationSyntax 或 FileScopedNamespaceDeclarationSyntax,后者为新版本引入的文件级命名空间声明)、类中的成员(例如方法 MethodDeclarationSyntax、属性 PropertyDeclarationSyntax)、方法中的每一条语句等等都是 syntax node,且不同的 node 之间可能嵌套,例如方法会嵌套在类中,语句会嵌套在方法中等等。
由各种不同的 syntax node 构成的 syntax tree 是仅包含原生语法的未编译和组织的原始结构,即它的结构完全取决于代码本身,且与代码是一一对应的,所以我们可以从任一 syntax node 获取 location(在源生成器代码中的变量名常简称 loc)即该结构在代码中的具体位置,可以在后续分析器警告等信息中用于位置标记。
Syntax node 在源生成器中常见于 syntax provider 的初步筛选(即 predicate 委托),此时可以根据语法结构快速定位源生成器查找目标的大致范围,并在后续 transform 中通过条件返回和 Where 进一步筛选具体目标。
但…实际上,在大多场景下,predicate 往往只有一行:
predicate: static (node, _) => node is PropertyDeclarationSyntax or MethodDeclarationSyntax
// 也可能是 ClassDeclarationSyntax 等,取决于你想要筛选什么声明
哈哈哈,没错。因为 syntax node 只能提供原始语法结构,很多信息想要获取只能手搓解析,但这样等于自己造编译器,常规场景下的开发几乎不可能这么做。这就导致 syntax 通常只能进行一些非常非常基础的筛选,于是在真实应用场景中我们几乎从来不直接访问和操作 syntax node,取而代之的则是下面的 symbol。
Symbol
Symbol,即符号,是 Roslyn 将整个项目的 syntax tree 根据语义规则翻译和抽象化后的、拥有完整属性和上下文的实体,它代表了代码的 semantic model(语义模型)。如果说 syntax 关注代码具体怎么写,那 symbol 则是关注代码真正的含义,即这段代码要做什么事。
在源生成器中,symbol 实例通常从 transform 委托提供的上下文中获取 ISymbol 并进行类型推断以取得,例如:
transform: static (ctx, _) =>
{
if (ctx.TargetSymbol is not IPropertySymbol prop) return default;
return prop; // 或者继续处理过的在生成上下文中使用的东西
}
这段代码通常在后面配合一个 Where 筛选来去掉由于不合法或不符合预期而返回 default 的值,结合上面的 predicate,最后的代码长这样:
private readonly record struct PropertyModel(
string DisplayName,
IPropertySymbol Property
);
public void Initialize(IncrementalGeneratorInitializationContext context)
{
// 收集所有属性用于后续处理
var properties = context.SyntaxProvider.CreateSyntaxProvider(
predicate: static (node, _) => node is PropertyDeclarationSyntax,
transform: static (ctx, _) =>
{
if (ctx.TargetSymbol is not IPropertySymbol prop) return default;
return new PropertyModel(prop.ToDisplayString(), prop);
})
.Where(static model => model != default)
.Collect();
}Symbol 包含大量属性和方法用于访问语义模型,可以获取名称、类型、修饰符、标记列表、文档注释等各种有用的信息。不同的 ISymbol 子类型还包含关于自身特性的信息,例如 ITypeSymbol 的父类型、泛型参数,IMethodSymbol 的参数、泛型参数,IPropertySymbol 的 getter/setter 等。
喜欢的话,留下你的评论吧~