如何从MemberAccessExpressionSyntax获取方法主体?

时间:2019-04-02 18:47:46

标签: c# abstract-syntax-tree roslyn

我正在使用Roslyn并试图构建一个分析器,该分析器将遍历方法的整个源(包括所有子方法),以便为用户提供洞察力。

我不知道如何进入MemberAccessExpression(指向方法),以便我可以开始浏览包含该方法的文件。

我尝试遍历ChildNodesChildTokens,也查看了调试器中的对象,但没有看到可用于加载MemberAccessExpression的类/方法的属性。指。

遍历树找到我感兴趣的探索方法后,我将执行以下操作:

private void AnalyzeSyntaxNode(SyntaxNode syntaxNode)
{
    if (this.foundMethod)
    {
        return;
    }

    if (syntaxNode is InvocationExpressionSyntax invocationExpressionSyntax)
    {
        this.foundMethod = true;
        PrintNodeAndChildren(invocationExpressionSyntax);
    }

    var children = syntaxNode.ChildNodes();

    foreach (var child in children)
    {
        AnalyzeSyntaxNode(child);
    }
}

private void PrintNodeAndChildren(SyntaxNode node)
{
    Console.WriteLine($"Child: {node}");

    var children = node.ChildNodes();

    if (children.Any())
    {
        foreach (var child in children)
        {
            PrintNodeAndChildren(child);
        }
    }
}

我想访问包含该方法的类以及该方法本身的主体。

在下面的示例中,我开始遍历Caller的语法树,并且我想访问Callee.DoSomethingElse的正文。实际上,我的目标是将_visited = true中的_visited = 99替换为Callee.DoSomethingElse,但是,如果我能弄清楚如何访问树的那部分,我感觉好像可以替换自己。

Caller.cs

public class Caller
{
    private Callee _callee = new Callee();
    public void DoSomething()
    {
        _callee.DoSomethingElse();
    }
}

Callee.cs(与Caller.cs不同的项目)

public class Callee
{
    private int _visited = 0;
    public void DoSomethingElse()
    {
        _visited = 1;
    }
}

这是一个任意的,荒谬的例子,但我觉得似乎可以理解。

1 个答案:

答案 0 :(得分:0)

事实证明,为此使用的正确类是CSharpSyntaxRewriter,是CSharpSyntaxVisitor的子类,可以大大简化语法树的导航。

它在功能上与CSharpSyntaxVisitor相同,但是允许修改语法节点。

这是我对此类的实现:

public class AssignmentReplacer : CSharpSyntaxRewriter
{
    private string inMethod;

    public override SyntaxNode VisitMethodDeclaration(MethodDeclarationSyntax node)
    {
        inMethod = node.Identifier.ValueText;

        return base.VisitMethodDeclaration(node);
    }

    public override SyntaxNode VisitAssignmentExpression(AssignmentExpressionSyntax node)
    {
        if (inMethod == "DoSomethingElse")
        {
            if (node.Left is IdentifierNameSyntax name &&
               name.Identifier.Text == "_visited")
            {

                return node.Update(node.Left,
                                   node.OperatorToken,
                                   SyntaxFactory.LiteralExpression(SyntaxKind.TrueLiteralExpression));
            }
        }

        return base.VisitAssignmentExpression(node);
    }
}

在深度优先的情况下,它会在私有字段中从VisitMethodDeclaration跟踪其当前方法的位置。它使用VisitAssignmentExpression中的值来确保修改节点时采用正确的方法。

一旦在正确的方法内标识了一个AssignmentExpressionSyntax节点,它将创建该节点的修改后的副本,并在右侧将其更改为新的LiteralExpression

您可能必须对此进行更多检查,以确保您正在修改正确的节点,但这适用于简单的示例数据。

您可以使用此类:

var programText =
@"public class Callee
{
    private bool _visited = false;
    public void DoSomethingElse()
    {
        _visited = false;
    }
}";

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

var visitor = new AssignmentReplacer();
var updated = visitor.Visit(root);

运行访问者后,updated将包含修改后的代码:

public class Callee
{
    private bool _visited = true;
    public void DoSomethingElse()
    {
        _visited = true;
    }
}

提醒一下,这是我第一次这样做,因此它可能不是最有效或最佳的方法。

谢谢你一个有趣的挑战!

可以here找到完整的LINQPad示例。