Skip to content

语法树 API

深入学习 Roslyn 语法树 API 的使用方法

📚 本文档内容

本文档详细介绍语法树 API 的使用,包括:

  • 语法树的获取和遍历
  • 常用语法节点类型
  • 语法节点的操作方法
  • 实用技巧和模式

📋 文档信息

项目信息
文档标题语法树 API
所属系列学习指南
难度级别⭐⭐⭐ 中级
预计阅读时间50 分钟
前置知识Roslyn API 介绍
相关文档语义模型 API

🎯 学习目标

学完本文档后,你将能够:

  • ✅ 理解语法树的结构和层次
  • ✅ 掌握语法树的获取和遍历方法
  • ✅ 熟练使用常用语法节点类型
  • ✅ 掌握语法节点的操作方法
  • ✅ 应用实用技巧和模式

📑 快速导航

章节内容概要跳转链接
语法树层次语法树的三个层次查看
获取和遍历语法树的获取和遍历方法查看
常用节点常用语法节点类型详解查看
节点方法语法节点的常用方法查看
实用技巧实用技巧和模式查看

概览

语法树(Syntax Tree)是 Roslyn 的核心概念,它将源代码表示为树形结构,每个节点代表代码的一个语法元素。

语法树的三个层次

1. SyntaxTree - 整个文件的语法树

SyntaxTree
└── CompilationUnitSyntax (根节点)
    ├── UsingDirectiveSyntax (using 语句)
    ├── NamespaceDeclarationSyntax (命名空间)
    │   └── ClassDeclarationSyntax (类)
    │       ├── SyntaxToken (public 关键字)
    │       ├── SyntaxToken (class 关键字)
    │       ├── SyntaxToken (类名标识符)
    │       └── MethodDeclarationSyntax (方法)
    │           ├── TypeSyntax (返回类型)
    │           ├── SyntaxToken (方法名)
    │           ├── ParameterListSyntax (参数列表)
    │           └── BlockSyntax (方法体)
    └── SyntaxTrivia (注释、空格等)

2. SyntaxNode - 语法节点

语法节点表示声明、语句、表达式等,例如:

  • ClassDeclarationSyntax - 类声明
  • MethodDeclarationSyntax - 方法声明
  • PropertyDeclarationSyntax - 属性声明

3. SyntaxToken - 语法标记

语法标记表示关键字、标识符、运算符等,例如:

  • public, class, {, }
  • 叶子节点,没有子节点

4. SyntaxTrivia - 语法琐碎内容

语法琐碎内容表示空格、注释、预处理指令等:

  • 附加在 Token 上
  • 不影响程序语义

获取和遍历语法树

基本操作

csharp
// 1. 解析代码文本创建语法树
string sourceCode = @"
using System;

namespace MyNamespace
{
    public class MyClass
    {
        public void MyMethod() { }
    }
}";

SyntaxTree tree = CSharpSyntaxTree.ParseText(sourceCode);

// 2. 获取根节点
CompilationUnitSyntax root = tree.GetCompilationUnitRoot();

// 或使用异步方法
CompilationUnitSyntax rootAsync = await tree.GetRootAsync() as CompilationUnitSyntax;

// 3. 获取文件路径
string filePath = tree.FilePath;

// 4. 获取编码
Encoding encoding = tree.Encoding;

// 5. 获取文本
SourceText text = tree.GetText();

遍历语法树的方法

csharp
// 方法 1: 使用 DescendantNodes() - 获取所有后代节点
var allNodes = root.DescendantNodes();

// 方法 2: 使用 ChildNodes() - 只获取直接子节点
var children = root.ChildNodes();

// 方法 3: 使用 DescendantNodesAndSelf() - 包含自身
var nodesIncludingSelf = root.DescendantNodesAndSelf();

// 方法 4: 使用 DescendantNodes() 的重载 - 控制遍历深度
var nodesWithPredicate = root.DescendantNodes(
    descendIntoChildren: node => node is not MethodDeclarationSyntax);

// 方法 5: 使用 OfType<T>() - 查找特定类型的节点
var classes = root.DescendantNodes().OfType<ClassDeclarationSyntax>();
var methods = root.DescendantNodes().OfType<MethodDeclarationSyntax>();
var properties = root.DescendantNodes().OfType<PropertyDeclarationSyntax>();

// 方法 6: 使用 LINQ 查询
var publicClasses = root.DescendantNodes()
    .OfType<ClassDeclarationSyntax>()
    .Where(c => c.Modifiers.Any(m => m.IsKind(SyntaxKind.PublicKeyword)));

// 方法 7: 使用 SyntaxWalker - 自定义遍历逻辑
public class MyWalker : CSharpSyntaxWalker
{
    public List<ClassDeclarationSyntax> Classes { get; } = new();
    
    public override void VisitClassDeclaration(ClassDeclarationSyntax node)
    {
        Classes.Add(node);
        base.VisitClassDeclaration(node); // 继续遍历子节点
    }
}

var walker = new MyWalker();
walker.Visit(root);
var foundClasses = walker.Classes;

常用语法节点类型详解

1. 类声明 (ClassDeclarationSyntax)

csharp
ClassDeclarationSyntax classDecl;

// 基本信息
string className = classDecl.Identifier.Text;              // 类名
string classNameWithTrivia = classDecl.Identifier.ValueText; // 类名(不含 trivia)

// 修饰符
SyntaxTokenList modifiers = classDecl.Modifiers;
bool isPublic = modifiers.Any(m => m.IsKind(SyntaxKind.PublicKeyword));
bool isPartial = modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword));
bool isAbstract = modifiers.Any(m => m.IsKind(SyntaxKind.AbstractKeyword));
bool isSealed = modifiers.Any(m => m.IsKind(SyntaxKind.SealedKeyword));
bool isStatic = modifiers.Any(m => m.IsKind(SyntaxKind.StaticKeyword));

// 基类和接口
BaseListSyntax? baseList = classDecl.BaseList;
if (baseList != null)
{
    foreach (var baseType in baseList.Types)
    {
        string typeName = baseType.Type.ToString();
        // 第一个通常是基类,其余是接口
    }
}

// 类型参数(泛型)
TypeParameterListSyntax? typeParameters = classDecl.TypeParameterList;
if (typeParameters != null)
{
    foreach (var typeParam in typeParameters.Parameters)
    {
        string paramName = typeParam.Identifier.Text;
    }
}

// 约束(泛型约束)
SyntaxList<TypeParameterConstraintClauseSyntax> constraints = classDecl.ConstraintClauses;

// 成员
SyntaxList<MemberDeclarationSyntax> members = classDecl.Members;
var methods = members.OfType<MethodDeclarationSyntax>();
var properties = members.OfType<PropertyDeclarationSyntax>();
var fields = members.OfType<FieldDeclarationSyntax>();
var constructors = members.OfType<ConstructorDeclarationSyntax>();

// 特性
SyntaxList<AttributeListSyntax> attributeLists = classDecl.AttributeLists;
foreach (var attrList in attributeLists)
{
    foreach (var attr in attrList.Attributes)
    {
        string attrName = attr.Name.ToString();
    }
}

// 位置信息
Location location = classDecl.GetLocation();
FileLinePositionSpan span = location.GetLineSpan();
int startLine = span.StartLinePosition.Line;
int endLine = span.EndLinePosition.Line;

2. 方法声明 (MethodDeclarationSyntax)

csharp
MethodDeclarationSyntax methodDecl;

// 基本信息
string methodName = methodDecl.Identifier.Text;
TypeSyntax returnType = methodDecl.ReturnType;
string returnTypeName = returnType.ToString();

// 修饰符
SyntaxTokenList modifiers = methodDecl.Modifiers;
bool isPublic = modifiers.Any(m => m.IsKind(SyntaxKind.PublicKeyword));
bool isStatic = modifiers.Any(m => m.IsKind(SyntaxKind.StaticKeyword));
bool isAsync = modifiers.Any(m => m.IsKind(SyntaxKind.AsyncKeyword));
bool isVirtual = modifiers.Any(m => m.IsKind(SyntaxKind.VirtualKeyword));
bool isOverride = modifiers.Any(m => m.IsKind(SyntaxKind.OverrideKeyword));

// 参数
ParameterListSyntax parameters = methodDecl.ParameterList;
foreach (var param in parameters.Parameters)
{
    string paramName = param.Identifier.Text;
    TypeSyntax paramType = param.Type;
    
    // 参数修饰符
    SyntaxTokenList paramModifiers = param.Modifiers;
    bool isRef = paramModifiers.Any(m => m.IsKind(SyntaxKind.RefKeyword));
    bool isOut = paramModifiers.Any(m => m.IsKind(SyntaxKind.OutKeyword));
    bool isParams = paramModifiers.Any(m => m.IsKind(SyntaxKind.ParamsKeyword));
    
    // 默认值
    EqualsValueClauseSyntax? defaultValue = param.Default;
    if (defaultValue != null)
    {
        string defaultExpr = defaultValue.Value.ToString();
    }
}

// 方法体
BlockSyntax? body = methodDecl.Body;
ArrowExpressionClauseSyntax? expressionBody = methodDecl.ExpressionBody;

if (body != null)
{
    // 传统方法体 { ... }
    var statements = body.Statements;
}
else if (expressionBody != null)
{
    // 表达式体 => ...
    var expression = expressionBody.Expression;
}

// 类型参数(泛型方法)
TypeParameterListSyntax? typeParameters = methodDecl.TypeParameterList;

// 约束
SyntaxList<TypeParameterConstraintClauseSyntax> constraints = methodDecl.ConstraintClauses;

3. 属性声明 (PropertyDeclarationSyntax)

csharp
PropertyDeclarationSyntax propertyDecl;

// 基本信息
string propertyName = propertyDecl.Identifier.Text;
TypeSyntax propertyType = propertyDecl.Type;

// 修饰符
SyntaxTokenList modifiers = propertyDecl.Modifiers;

// 访问器(get/set/init)
AccessorListSyntax? accessors = propertyDecl.AccessorList;
if (accessors != null)
{
    foreach (var accessor in accessors.Accessors)
    {
        SyntaxKind kind = accessor.Kind();
        // SyntaxKind.GetAccessorDeclaration
        // SyntaxKind.SetAccessorDeclaration
        // SyntaxKind.InitAccessorDeclaration (C# 9+)
        
        // 访问器修饰符
        SyntaxTokenList accessorModifiers = accessor.Modifiers;
        bool isPrivate = accessorModifiers.Any(m => m.IsKind(SyntaxKind.PrivateKeyword));
        
        // 访问器体
        BlockSyntax? accessorBody = accessor.Body;
        ArrowExpressionClauseSyntax? accessorExpressionBody = accessor.ExpressionBody;
    }
}

// 表达式体属性(C# 6+)
ArrowExpressionClauseSyntax? expressionBody = propertyDecl.ExpressionBody;
if (expressionBody != null)
{
    // public int Value => _value;
    var expression = expressionBody.Expression;
}

// 初始化器(C# 6+)
EqualsValueClauseSyntax? initializer = propertyDecl.Initializer;
if (initializer != null)
{
    // public int Value { get; set; } = 42;
    var initExpression = initializer.Value;
}

4. 字段声明 (FieldDeclarationSyntax)

csharp
FieldDeclarationSyntax fieldDecl;

// 修饰符
SyntaxTokenList modifiers = fieldDecl.Modifiers;
bool isPrivate = modifiers.Any(m => m.IsKind(SyntaxKind.PrivateKeyword));
bool isReadOnly = modifiers.Any(m => m.IsKind(SyntaxKind.ReadOnlyKeyword));
bool isConst = modifiers.Any(m => m.IsKind(SyntaxKind.ConstKeyword));
bool isStatic = modifiers.Any(m => m.IsKind(SyntaxKind.StaticKeyword));

// 变量声明
VariableDeclarationSyntax declaration = fieldDecl.Declaration;
TypeSyntax fieldType = declaration.Type;

// 变量(一个声明可能包含多个变量)
SeparatedSyntaxList<VariableDeclaratorSyntax> variables = declaration.Variables;
foreach (var variable in variables)
{
    string fieldName = variable.Identifier.Text;
    
    // 初始化器
    EqualsValueClauseSyntax? initializer = variable.Initializer;
    if (initializer != null)
    {
        var initExpression = initializer.Value;
    }
}

// 示例:
// private int _x = 1, _y = 2;
// 这会产生一个 FieldDeclarationSyntax,包含两个 VariableDeclaratorSyntax

5. 特性 (AttributeSyntax)

csharp
AttributeListSyntax attributeList;

// 目标(可选)
SyntaxToken? target = attributeList.Target?.Identifier;
// [assembly: ...], [return: ...], [field: ...] 等

// 特性列表
SeparatedSyntaxList<AttributeSyntax> attributes = attributeList.Attributes;

foreach (var attribute in attributes)
{
    // 特性名称
    NameSyntax attributeName = attribute.Name;
    string name = attributeName.ToString();
    
    // 参数列表
    AttributeArgumentListSyntax? argumentList = attribute.ArgumentList;
    if (argumentList != null)
    {
        foreach (var arg in argumentList.Arguments)
        {
            // 命名参数
            NameEqualsSyntax? nameEquals = arg.NameEquals;
            if (nameEquals != null)
            {
                string paramName = nameEquals.Name.Identifier.Text;
            }
            
            // 参数表达式
            ExpressionSyntax expression = arg.Expression;
            string value = expression.ToString();
        }
    }
}

// 示例:
// [MyAttribute("value", Name = "test")]
// attributeName = "MyAttribute"
// 第一个参数: expression = "value"
// 第二个参数: nameEquals.Name = "Name", expression = "test"

6. 命名空间声明

csharp
// 传统命名空间 (C# 1.0+)
NamespaceDeclarationSyntax namespaceDecl;
NameSyntax namespaceName = namespaceDecl.Name;
string name = namespaceName.ToString(); // "MyNamespace" 或 "My.Nested.Namespace"

// 成员
SyntaxList<MemberDeclarationSyntax> members = namespaceDecl.Members;

// 文件范围命名空间 (C# 10+)
FileScopedNamespaceDeclarationSyntax fileScopedNs;
NameSyntax fileScopedName = fileScopedNs.Name;

// 检查命名空间类型
if (node is NamespaceDeclarationSyntax traditionalNs)
{
    // 传统命名空间
}
else if (node is FileScopedNamespaceDeclarationSyntax fileScopedNs)
{
    // 文件范围命名空间
}

7. 其他常用节点

csharp
// 接口声明
InterfaceDeclarationSyntax interfaceDecl;

// 结构声明
StructDeclarationSyntax structDecl;

// 枚举声明
EnumDeclarationSyntax enumDecl;
foreach (var member in enumDecl.Members)
{
    string memberName = member.Identifier.Text;
    EqualsValueClauseSyntax? value = member.EqualsValue;
}

// 委托声明
DelegateDeclarationSyntax delegateDecl;

// 记录声明 (C# 9+)
RecordDeclarationSyntax recordDecl;

// 构造函数
ConstructorDeclarationSyntax constructorDecl;
ConstructorInitializerSyntax? initializer = constructorDecl.Initializer;
// : base(...) 或 : this(...)

// 析构函数
DestructorDeclarationSyntax destructorDecl;

// 索引器
IndexerDeclarationSyntax indexerDecl;
BracketedParameterListSyntax parameters = indexerDecl.ParameterList;

// 事件
EventDeclarationSyntax eventDecl;
EventFieldDeclarationSyntax eventFieldDecl;

// 运算符重载
OperatorDeclarationSyntax operatorDecl;
ConversionOperatorDeclarationSyntax conversionOperatorDecl;

语法节点的常用方法

csharp
SyntaxNode node;

// ============================================================
// 导航方法
// ============================================================

// 获取父节点
SyntaxNode? parent = node.Parent;

// 获取直接子节点
IEnumerable<SyntaxNode> children = node.ChildNodes();

// 获取所有后代节点
IEnumerable<SyntaxNode> descendants = node.DescendantNodes();

// 获取所有后代节点(包含自身)
IEnumerable<SyntaxNode> descendantsAndSelf = node.DescendantNodesAndSelf();

// 获取祖先节点
IEnumerable<SyntaxNode> ancestors = node.Ancestors();

// 获取祖先节点(包含自身)
IEnumerable<SyntaxNode> ancestorsAndSelf = node.AncestorsAndSelf();

// 查找特定类型的祖先
ClassDeclarationSyntax? containingClass = node.FirstAncestorOrSelf<ClassDeclarationSyntax>();
NamespaceDeclarationSyntax? containingNamespace = node.FirstAncestorOrSelf<NamespaceDeclarationSyntax>();

// ============================================================
// Token 和 Trivia 方法
// ============================================================

// 获取第一个和最后一个 Token
SyntaxToken firstToken = node.GetFirstToken();
SyntaxToken lastToken = node.GetLastToken();

// 获取所有 Token
IEnumerable<SyntaxToken> tokens = node.DescendantTokens();

// 获取前导 Trivia(空格、注释等)
SyntaxTriviaList leadingTrivia = node.GetLeadingTrivia();

// 获取尾随 Trivia
SyntaxTriviaList trailingTrivia = node.GetTrailingTrivia();

// ============================================================
// 位置和范围方法
// ============================================================

// 获取位置信息
Location location = node.GetLocation();
FileLinePositionSpan lineSpan = location.GetLineSpan();
int startLine = lineSpan.StartLinePosition.Line;
int startColumn = lineSpan.StartLinePosition.Character;
int endLine = lineSpan.EndLinePosition.Line;
int endColumn = lineSpan.EndLinePosition.Character;

// 获取文本范围
TextSpan span = node.Span;
int start = span.Start;
int end = span.End;
int length = span.Length;

// 获取完整范围(包含 Trivia)
TextSpan fullSpan = node.FullSpan;

// ============================================================
// 文本方法
// ============================================================

// 获取文本(不含 Trivia)
string text = node.ToString();

// 获取完整文本(含 Trivia)
string fullText = node.ToFullString();

// ============================================================
// 类型检查方法
// ============================================================

// 检查节点类型
bool isClass = node is ClassDeclarationSyntax;
bool isMethod = node is MethodDeclarationSyntax;

// 使用 Kind() 方法
SyntaxKind kind = node.Kind();
bool isClassDeclaration = kind == SyntaxKind.ClassDeclaration;

// 使用 IsKind() 方法
bool isPublicKeyword = token.IsKind(SyntaxKind.PublicKeyword);

// ============================================================
// 修改方法(返回新节点,不修改原节点)
// ============================================================

// 替换节点
SyntaxNode newTree = root.ReplaceNode(oldNode, newNode);

// 替换多个节点
SyntaxNode newTree2 = root.ReplaceNodes(
    nodesToReplace,
    (original, rewritten) => newNode);

// 替换 Token
SyntaxNode newTree3 = root.ReplaceToken(oldToken, newToken);

// 添加 Trivia
SyntaxNode nodeWithComment = node.WithLeadingTrivia(
    SyntaxFactory.Comment("// This is a comment"));

// 移除 Trivia
SyntaxNode nodeWithoutTrivia = node.WithoutLeadingTrivia();

// ============================================================
// 其他实用方法
// ============================================================

// 检查是否包含诊断错误
bool hasErrors = node.ContainsDiagnostics;

// 检查是否缺失(语法错误)
bool isMissing = node.IsMissing;

// 检查是否在结构上等价
bool isEquivalent = node.IsEquivalentTo(otherNode);

// 获取语法树
SyntaxTree? tree = node.SyntaxTree;

实用技巧和模式

1. 安全地获取嵌套节点

csharp
// 不好的做法(可能抛出 NullReferenceException)
var className = node.Parent.Parent.Parent as ClassDeclarationSyntax;

// 好的做法(使用 FirstAncestorOrSelf)
var className = node.FirstAncestorOrSelf<ClassDeclarationSyntax>();
if (className != null)
{
    // 安全地使用
}

2. 查找特定模式的代码

csharp
// 查找所有返回 void 的公共方法
var voidPublicMethods = root.DescendantNodes()
    .OfType<MethodDeclarationSyntax>()
    .Where(m => 
        m.Modifiers.Any(mod => mod.IsKind(SyntaxKind.PublicKeyword)) &&
        m.ReturnType.ToString() == "void");

// 查找所有自动属性
var autoProperties = root.DescendantNodes()
    .OfType<PropertyDeclarationSyntax>()
    .Where(p => p.AccessorList != null &&
                p.AccessorList.Accessors.All(a => a.Body == null && a.ExpressionBody == null));

// 查找所有带有特定特性的成员
var attributedMembers = root.DescendantNodes()
    .OfType<MemberDeclarationSyntax>()
    .Where(m => m.AttributeLists.Any(al =>
        al.Attributes.Any(a => a.Name.ToString() == "MyAttribute")));

3. 处理泛型类型

csharp
// 检查是否是泛型类
if (classDecl.TypeParameterList != null)
{
    int typeParamCount = classDecl.TypeParameterList.Parameters.Count;
    
    // 获取类型参数名称
    var typeParamNames = classDecl.TypeParameterList.Parameters
        .Select(p => p.Identifier.Text)
        .ToList();
    
    // 获取约束
    foreach (var constraint in classDecl.ConstraintClauses)
    {
        string typeParamName = constraint.Name.Identifier.Text;
        
        foreach (var constraintType in constraint.Constraints)
        {
            if (constraintType is TypeConstraintSyntax typeConstraint)
            {
                string constraintTypeName = typeConstraint.Type.ToString();
            }
            else if (constraintType is ClassOrStructConstraintSyntax classOrStruct)
            {
                bool isClass = classOrStruct.ClassOrStructKeyword.IsKind(SyntaxKind.ClassKeyword);
                bool isStruct = classOrStruct.ClassOrStructKeyword.IsKind(SyntaxKind.StructKeyword);
            }
            else if (constraintType is ConstructorConstraintSyntax)
            {
                // new() 约束
            }
        }
    }
}

4. 处理可空引用类型 (C# 8+)

csharp
// 检查是否是可空引用类型
if (typeSyntax is NullableTypeSyntax nullableType)
{
    TypeSyntax elementType = nullableType.ElementType;
    // string? -> elementType = "string"
}

// 检查是否有可空注解
if (typeSyntax is IdentifierNameSyntax identifierName)
{
    // 需要使用语义模型来确定可空性
}

💡 关键要点

  1. 语法树是不可变的

    • 所有修改操作都返回新的节点
    • 原始节点不会被改变
  2. 使用正确的遍历方法

    • DescendantNodes() - 获取所有后代
    • ChildNodes() - 只获取直接子节点
    • OfType<T>() - 过滤特定类型
  3. 安全地导航节点树

    • 使用 FirstAncestorOrSelf<T>() 查找祖先
    • 检查 null 值
    • 使用模式匹配
  4. 理解 Token 和 Trivia

    • Token 是语法的最小单位
    • Trivia 包含空格和注释
    • 使用 ToString() vs ToFullString()
  5. 性能考虑

    • 语法分析比语义分析快
    • 先用语法过滤再用语义验证
    • 避免重复遍历

🔗 相关文档


📚 下一步

学习完语法树 API 后,建议继续学习:

  1. 语义模型 API - 掌握语义分析技巧
  2. 常见模式 - 学习实用的代码模式
  3. 最佳实践 - 了解性能优化方法

基于 MIT 许可发布