Roslyn 源生成器与预处理编程(二)

发布于 2026-08-09 10:29 896 字 5 min read

ruattd avatar

ruattd

🍰 / .NET / BE / INTJ / ACG / 🍏 / 米游玩家 / 下落式狂魔

接前文,这节主要写写 C# 在编译器体系中的结构。 在 Roslyn 中,常用 syntax node 和 symbol 两大模型来表达代码。 Syntax Node Syntax node(语法节点)是 C# syntax tree(语法树)的基本成分,一个类()、一个接口()、一个 using 语句()、一个命名空间声明( 或 ,后者为新版本引入的文件级命名空间声明)、类中的成员(例如方法...

前言

接前文,这节主要写写 C# 在编译器体系中的结构。

在 Roslyn 中,常用 syntax node 和 symbol 两大模型来表达代码。

Syntax Node

Syntax node(语法节点)是 C# syntax tree(语法树)的基本成分,一个类(ClassDeclarationSyntax)、一个接口(InterfaceDeclarationSyntax)、一个 using 语句(UsingDirectiveSyntax)、一个命名空间声明(NamespaceDeclarationSyntaxFileScopedNamespaceDeclarationSyntax,后者为新版本引入的文件级命名空间声明)、类中的成员(例如方法 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 等。

喜欢的话,留下你的评论吧~