我正在使用Roslyn并尝试构建一个分析器,它将遍历方法的整个源(包括所有子方法),以便为用户提供洞察力。
对我来说,如何进入MemberAccessExpression(指向该方法)以便我可以开始浏览包含该方法的文件是不明智的。
我已经尝试迭代ChildNodes
和ChildTokens
,我也查看了调试器中的对象,我看到没有可用于加载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
的SyntaxTree,我想访问Callee.DoSomethingElse
的主体。实际上,我的目标是用_visited = true
中的_visited = 99
替换Callee.DoSomethingElse
,但是如果我能弄清楚如何访问树的那一部分,我觉得我可以自己替换节点。
public class Caller
{
private Callee _callee = new Callee();
public void DoSomething()
{
_callee.DoSomethingElse();
}
}
public class Callee
{
private int _visited = 0;
public void DoSomethingElse()
{
_visited = 1;
}
}
这是一个任意的,荒谬的例子,但我觉得好像它得到了重点。
事实证明,用于此的正确类是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; } }
同样谨慎,这是我第一次做这些,所以它可能不是最有效或最好的方法。
感谢您提出有趣的挑战!
可以找到一个完整工作的LINQPad示例here。