语法树 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,包含两个 VariableDeclaratorSyntax5. 特性 (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)
{
// 需要使用语义模型来确定可空性
}💡 关键要点
语法树是不可变的
- 所有修改操作都返回新的节点
- 原始节点不会被改变
使用正确的遍历方法
DescendantNodes()- 获取所有后代ChildNodes()- 只获取直接子节点OfType<T>()- 过滤特定类型
安全地导航节点树
- 使用
FirstAncestorOrSelf<T>()查找祖先 - 检查 null 值
- 使用模式匹配
- 使用
理解 Token 和 Trivia
- Token 是语法的最小单位
- Trivia 包含空格和注释
- 使用
ToString()vsToFullString()
性能考虑
- 语法分析比语义分析快
- 先用语法过滤再用语义验证
- 避免重复遍历
🔗 相关文档
- Roslyn API 介绍 - API 基础概念
- 语义模型 API - 语义分析详解
- 常见模式 - 实用代码模式
- 最佳实践 - 性能优化建议
- 返回学习指南
📚 下一步
学习完语法树 API 后,建议继续学习: