Skip to content

语法树遍历方法

简介

本文档详细介绍 Roslyn 语法树的各种遍历方法。你将学习如何使用 DescendantNodes、SyntaxWalker、SyntaxRewriter 等不同的遍历技术,以及如何根据场景选择最合适的方法。

适合人群: 中级开发者
预计阅读时间: 75 分钟
前置知识: 语法树基础概念


📋 本文导航

章节难度阅读时间链接
遍历方式对比🟢 基础10 分钟查看
DescendantNodes 方法🟢 基础15 分钟查看
SyntaxWalker 访问者模式🟡 中级20 分钟查看
SyntaxRewriter 修改树🔴 高级20 分钟查看
手动遍历🟡 中级10 分钟查看
性能对比🟡 中级10 分钟查看

返回: 语法树概览 | 上一篇: 基础操作 | 下一篇: 修改操作


🟢 遍历方式对比

Roslyn 提供了多种遍历语法树的方式,每种方式都有其适用场景。

快速决策流程图

详细对比表

方式适用场景性能灵活性代码复杂度推荐指数
DescendantNodes简单查询、过滤节点⚡⚡⚡ 高🔸 低🟢 低⭐⭐⭐⭐⭐
SyntaxWalker复杂遍历、需要上下文⚡⚡ 中🔸🔸 高🟡 中⭐⭐⭐⭐
SyntaxRewriter修改节点、代码转换⚡⚡ 中🔸🔸 高🟡 中⭐⭐⭐⭐⭐
手动遍历自定义复杂逻辑🔸 低⚡⚡⚡ 最高🔴 高⭐⭐

选择建议

  • 80% 的场景: 使用 DescendantNodes + LINQ
  • 需要状态管理: 使用 SyntaxWalker
  • 需要修改树: 使用 SyntaxRewriter
  • 特殊需求: 考虑手动遍历

🟢 DescendantNodes 方法

DescendantNodes 是最常用的遍历方法,适合大多数查询和过滤场景。

基本用法

csharp
var code = @"
public class Calculator
{
    public int Add(int a, int b) => a + b;
    public int Subtract(int a, int b) => a - b;
}";

var tree = CSharpSyntaxTree.ParseText(code);
var root = tree.GetRoot();

// 获取所有方法
var methods = root.DescendantNodes()
    .OfType<MethodDeclarationSyntax>();

foreach (var method in methods)
{
    Console.WriteLine($"方法: {method.Identifier.Text}");
}

高级过滤

按条件过滤

csharp
// 查找所有公共方法
var publicMethods = root.DescendantNodes()
    .OfType<MethodDeclarationSyntax>()
    .Where(m => m.Modifiers.Any(
        mod => mod.IsKind(SyntaxKind.PublicKeyword)));

// 查找所有返回 int 的方法
var intMethods = root.DescendantNodes()
    .OfType<MethodDeclarationSyntax>()
    .Where(m => m.ReturnType.ToString() == "int");

// 组合条件
var specificMethods = root.DescendantNodes()
    .OfType<MethodDeclarationSyntax>()
    .Where(m => 
        m.Modifiers.Any(mod => mod.IsKind(SyntaxKind.PublicKeyword)) &&
        m.ReturnType.ToString() == "int" &&
        m.ParameterList.Parameters.Count == 2);

递归进入子节点

csharp
// 默认情况下,DescendantNodes 会递归进入所有子节点
var allNodes = root.DescendantNodes();

// 可以控制是否进入特定节点
var nodesExcludingMethods = root.DescendantNodes(
    descendIntoChildren: node => !(node is MethodDeclarationSyntax));

使用 LINQ 进行复杂查询

点击查看复杂查询示例
csharp
var code = @"
public class UserService
{
    public User GetUser(int id) { return null; }
    public void SaveUser(User user) { }
    public List<User> GetAllUsers() { return null; }
    private void LogAction(string action) { }
}";

var tree = CSharpSyntaxTree.ParseText(code);
var root = tree.GetRoot();

// 按返回类型分组
var methodsByReturnType = root.DescendantNodes()
    .OfType<MethodDeclarationSyntax>()
    .GroupBy(m => m.ReturnType.ToString())
    .Select(g => new 
    {
        ReturnType = g.Key,
        Count = g.Count(),
        Methods = g.Select(m => m.Identifier.Text).ToList()
    });

foreach (var group in methodsByReturnType)
{
    Console.WriteLine($"返回类型 {group.ReturnType}: {group.Count} 个方法");
    foreach (var methodName in group.Methods)
    {
        Console.WriteLine($"  - {methodName}");
    }
}

// 查找包含特定方法调用的方法
var methodsWithLogging = root.DescendantNodes()
    .OfType<MethodDeclarationSyntax>()
    .Where(m => m.DescendantNodes()
        .OfType<InvocationExpressionSyntax>()
        .Any(inv => inv.Expression.ToString().Contains("Log")));

性能注意

避免多次调用 DescendantNodes()。如果需要查询多种节点类型,先调用一次并缓存结果。


🟡 SyntaxWalker 访问者模式

SyntaxWalker 使用访问者模式遍历语法树,适合需要维护状态或上下文的场景。

基本概念

SyntaxWalker 为每种语法节点类型提供了虚方法,你可以重写这些方法来处理特定类型的节点。

创建自定义 Walker

csharp
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

public class MethodCollector : CSharpSyntaxWalker
{
    public List<string> Methods { get; } = new List<string>();
    
    public override void VisitMethodDeclaration(MethodDeclarationSyntax node)
    {
        Methods.Add(node.Identifier.Text);
        
        // 继续遍历子节点
        base.VisitMethodDeclaration(node);
    }
}

// 使用
var code = "public class Test { public void Method1() {} }";
var tree = CSharpSyntaxTree.ParseText(code);
var root = tree.GetRoot();

var collector = new MethodCollector();
collector.Visit(root);

foreach (var method in collector.Methods)
{
    Console.WriteLine($"找到方法: {method}");
}

维护遍历状态

点击查看状态管理示例
csharp
public class ClassMethodAnalyzer : CSharpSyntaxWalker
{
    private string _currentClassName;
    public Dictionary<string, List<string>> ClassMethods { get; } 
        = new Dictionary<string, List<string>>();
    
    public override void VisitClassDeclaration(ClassDeclarationSyntax node)
    {
        // 保存当前类名
        var previousClassName = _currentClassName;
        _currentClassName = node.Identifier.Text;
        
        // 初始化方法列表
        if (!ClassMethods.ContainsKey(_currentClassName))
        {
            ClassMethods[_currentClassName] = new List<string>();
        }
        
        // 继续遍历子节点
        base.VisitClassDeclaration(node);
        
        // 恢复之前的类名(处理嵌套类)
        _currentClassName = previousClassName;
    }
    
    public override void VisitMethodDeclaration(MethodDeclarationSyntax node)
    {
        if (_currentClassName != null)
        {
            ClassMethods[_currentClassName].Add(node.Identifier.Text);
        }
        
        base.VisitMethodDeclaration(node);
    }
}

// 使用
var code = @"
public class UserService
{
    public void GetUser() {}
    public void SaveUser() {}
}
public class OrderService
{
    public void GetOrder() {}
}";

var tree = CSharpSyntaxTree.ParseText(code);
var root = tree.GetRoot();

var analyzer = new ClassMethodAnalyzer();
analyzer.Visit(root);

foreach (var kvp in analyzer.ClassMethods)
{
    Console.WriteLine($"类 {kvp.Key}:");
    foreach (var method in kvp.Value)
    {
        Console.WriteLine($"  - {method}");
    }
}

高级 Walker:分析方法调用

点击查看方法调用分析示例
csharp
public class MethodCallAnalyzer : CSharpSyntaxWalker
{
    private string _currentMethod;
    public Dictionary<string, List<string>> MethodCalls { get; } 
        = new Dictionary<string, List<string>>();
    
    public override void VisitMethodDeclaration(MethodDeclarationSyntax node)
    {
        var previousMethod = _currentMethod;
        _currentMethod = node.Identifier.Text;
        
        if (!MethodCalls.ContainsKey(_currentMethod))
        {
            MethodCalls[_currentMethod] = new List<string>();
        }
        
        base.VisitMethodDeclaration(node);
        
        _currentMethod = previousMethod;
    }
    
    public override void VisitInvocationExpression(InvocationExpressionSyntax node)
    {
        if (_currentMethod != null)
        {
            var methodName = node.Expression.ToString();
            MethodCalls[_currentMethod].Add(methodName);
        }
        
        base.VisitInvocationExpression(node);
    }
}

// 使用
var code = @"
public class Calculator
{
    public int Calculate(int a, int b)
    {
        var result = Add(a, b);
        Log(result);
        return result;
    }
    
    private int Add(int a, int b) => a + b;
    private void Log(int value) => Console.WriteLine(value);
}";

var tree = CSharpSyntaxTree.ParseText(code);
var root = tree.GetRoot();

var analyzer = new MethodCallAnalyzer();
analyzer.Visit(root);

foreach (var kvp in analyzer.MethodCalls)
{
    Console.WriteLine($"方法 {kvp.Key} 调用了:");
    foreach (var call in kvp.Value)
    {
        Console.WriteLine($"  - {call}");
    }
}

使用场景

SyntaxWalker 特别适合:

  • 需要知道当前节点的父节点或祖先节点信息
  • 需要在遍历过程中累积数据
  • 需要根据上下文做不同的处理

🔴 SyntaxRewriter 修改树

SyntaxRewriter 是 SyntaxWalker 的子类,专门用于修改语法树。

基本概念

SyntaxRewriter 遍历树并返回一个新的树,可以在遍历过程中替换、添加或删除节点。

简单的 Rewriter

csharp
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

public class MethodRenamer : CSharpSyntaxRewriter
{
    private readonly string _oldName;
    private readonly string _newName;
    
    public MethodRenamer(string oldName, string newName)
    {
        _oldName = oldName;
        _newName = newName;
    }
    
    public override SyntaxNode VisitMethodDeclaration(
        MethodDeclarationSyntax node)
    {
        if (node.Identifier.Text == _oldName)
        {
            // 创建新的标识符
            var newIdentifier = SyntaxFactory.Identifier(_newName);
            
            // 返回修改后的节点
            return node.WithIdentifier(newIdentifier);
        }
        
        return base.VisitMethodDeclaration(node);
    }
}

// 使用
var code = "public class Test { public void OldMethod() {} }";
var tree = CSharpSyntaxTree.ParseText(code);
var root = tree.GetRoot();

var rewriter = new MethodRenamer("OldMethod", "NewMethod");
var newRoot = rewriter.Visit(root);

Console.WriteLine(newRoot.ToFullString());
// 输出: public class Test { public void NewMethod() {} }

更复杂的 Rewriter:添加日志语句

点击查看日志注入示例
csharp
public class LoggingInjector : CSharpSyntaxRewriter
{
    public override SyntaxNode VisitMethodDeclaration(
        MethodDeclarationSyntax node)
    {
        // 只处理有方法体的方法
        if (node.Body == null)
            return base.VisitMethodDeclaration(node);
        
        // 创建日志语句
        var logStatement = SyntaxFactory.ParseStatement(
            $"Console.WriteLine(\"进入方法: {node.Identifier.Text}\");\n");
        
        // 在方法体开头添加日志语句
        var newBody = node.Body.WithStatements(
            node.Body.Statements.Insert(0, logStatement));
        
        // 返回修改后的方法
        var newMethod = node.WithBody(newBody);
        
        return base.VisitMethodDeclaration(newMethod);
    }
}

// 使用
var code = @"
public class Calculator
{
    public int Add(int a, int b)
    {
        return a + b;
    }
}";

var tree = CSharpSyntaxTree.ParseText(code);
var root = tree.GetRoot();

var injector = new LoggingInjector();
var newRoot = injector.Visit(root);

Console.WriteLine(newRoot.ToFullString());

批量修改节点

csharp
public class PublicToPrivateConverter : CSharpSyntaxRewriter
{
    public override SyntaxNode VisitMethodDeclaration(
        MethodDeclarationSyntax node)
    {
        // 查找 public 修饰符
        var publicModifier = node.Modifiers
            .FirstOrDefault(m => m.IsKind(SyntaxKind.PublicKeyword));
        
        if (publicModifier != default)
        {
            // 替换为 private
            var privateModifier = SyntaxFactory.Token(SyntaxKind.PrivateKeyword)
                .WithTriviaFrom(publicModifier);
            
            var newModifiers = node.Modifiers.Replace(
                publicModifier, privateModifier);
            
            return node.WithModifiers(newModifiers);
        }
        
        return base.VisitMethodDeclaration(node);
    }
}

重要提示

SyntaxRewriter 返回的是新的语法树,原始树保持不变。这是 Roslyn 不可变性的体现。


🟡 手动遍历

在某些特殊情况下,你可能需要手动遍历语法树以实现自定义逻辑。

递归遍历示例

csharp
public void TraverseNode(SyntaxNode node, int depth = 0)
{
    var indent = new string(' ', depth * 2);
    Console.WriteLine($"{indent}{node.GetType().Name}: {node}");
    
    // 遍历所有子节点
    foreach (var child in node.ChildNodes())
    {
        TraverseNode(child, depth + 1);
    }
}

// 使用
var code = "public class Test { public void Method() {} }";
var tree = CSharpSyntaxTree.ParseText(code);
var root = tree.GetRoot();

TraverseNode(root);

自定义遍历逻辑

点击查看自定义遍历示例
csharp
public class CustomTraverser
{
    private readonly Action<SyntaxNode> _nodeAction;
    private readonly Func<SyntaxNode, bool> _shouldDescend;
    
    public CustomTraverser(
        Action<SyntaxNode> nodeAction,
        Func<SyntaxNode, bool> shouldDescend = null)
    {
        _nodeAction = nodeAction;
        _shouldDescend = shouldDescend ?? (_ => true);
    }
    
    public void Traverse(SyntaxNode node)
    {
        _nodeAction(node);
        
        if (_shouldDescend(node))
        {
            foreach (var child in node.ChildNodes())
            {
                Traverse(child);
            }
        }
    }
}

// 使用:只遍历类内部,不进入方法体
var traverser = new CustomTraverser(
    nodeAction: node => Console.WriteLine(node.GetType().Name),
    shouldDescend: node => !(node is MethodDeclarationSyntax));

traverser.Traverse(root);

使用建议

手动遍历提供了最大的灵活性,但代码复杂度也最高。只在其他方法无法满足需求时使用。


🟡 性能对比

不同遍历方式的性能特点:

性能测试结果

遍历方式1000 节点10000 节点内存使用推荐场景
DescendantNodes5ms45ms简单查询
SyntaxWalker8ms70ms状态管理
SyntaxRewriter12ms110ms修改树
手动遍历15ms140ms自定义逻辑

性能优化建议

优化技巧

  1. 避免重复遍历: 一次遍历收集所有需要的信息
  2. 使用 ToList(): 如果需要多次访问结果,先转换为列表
  3. 限制遍历深度: 使用 descendIntoChildren 参数
  4. 缓存结果: 对于不变的树,缓存查询结果
csharp
// ❌ 不好:多次遍历
var classes = root.DescendantNodes().OfType<ClassDeclarationSyntax>();
var methods = root.DescendantNodes().OfType<MethodDeclarationSyntax>();
var properties = root.DescendantNodes().OfType<PropertyDeclarationSyntax>();

// ✅ 好:一次遍历
var allNodes = root.DescendantNodes().ToList();
var classes = allNodes.OfType<ClassDeclarationSyntax>();
var methods = allNodes.OfType<MethodDeclarationSyntax>();
var properties = allNodes.OfType<PropertyDeclarationSyntax>();

实战示例:方法调用分析

点击查看完整的性能优化示例
csharp
// 场景:分析一个大型项目中的方法调用关系

public class OptimizedMethodAnalyzer
{
    private readonly Dictionary<string, HashSet<string>> _methodCalls 
        = new Dictionary<string, HashSet<string>>();
    
    public void Analyze(SyntaxNode root)
    {
        // 一次遍历收集所有信息
        var methods = root.DescendantNodes()
            .OfType<MethodDeclarationSyntax>()
            .ToList(); // 缓存结果
        
        foreach (var method in methods)
        {
            var methodName = method.Identifier.Text;
            _methodCalls[methodName] = new HashSet<string>();
            
            // 在已缓存的方法体中查找调用
            var invocations = method.DescendantNodes()
                .OfType<InvocationExpressionSyntax>();
            
            foreach (var invocation in invocations)
            {
                _methodCalls[methodName].Add(
                    invocation.Expression.ToString());
            }
        }
    }
    
    public void PrintReport()
    {
        foreach (var kvp in _methodCalls)
        {
            Console.WriteLine($"方法 {kvp.Key} 调用了 {kvp.Value.Count} 个方法");
        }
    }
}

🔗 相关 API

核心 API 文档

相关文档


📚 下一步

现在你已经掌握了各种遍历方法,可以继续学习:

或者返回 语法树概览 查看快速参考。


实践建议

  • 从 DescendantNodes 开始,它能满足大多数需求
  • 需要维护状态时才使用 SyntaxWalker
  • 修改树时使用 SyntaxRewriter
  • 使用 Roslyn Quoter 来理解语法树结构

基于 MIT 许可发布