c# - 使用 Roslyn 替换方法节点

标签 c# roslyn

在探索 Roslyn 时,我组装了一个小应用程序,该应用程序应包含一个跟踪语句,作为 Visual Studio 解决方案中找到的每个方法的第一个语句。我的代码有问题,只更新第一种方法。

没有按预期工作的行被标记为“TODO”注释。请指教。

我也欢迎可以创建更精简/可读性解决方案的样式建议。

提前致谢。

...

    private void TraceBtn_Click(object sender, RoutedEventArgs e) {
        var myWorkSpace = new MyWorkspace("...Visual Studio 2012\Projects\Tests.sln"); 
        myWorkSpace.InjectTrace();
        myWorkSpace.ApplyChanges();
    }

...

using System;
using System.Linq;
using Roslyn.Compilers;
using Roslyn.Compilers.CSharp;
using Roslyn.Services;

namespace InjectTrace
{
    public class MyWorkspace
    {
    private string solutionFile;
    public string SolutionFile {
        get { return solutionFile; }
        set { 
            if (string.IsNullOrEmpty(value)) throw new Exception("Invalid Solution File");
            solutionFile = value;
        }
    }

    private IWorkspace loadedWorkSpace;
    public IWorkspace LoadedWorkSpace { get { return loadedWorkSpace; } }

    public ISolution CurrentSolution { get; private set; }
    public IProject CurrentProject { get; private set; }
    public IDocument CurrentDocument { get; private set; }
    public ISolution NewSolution { get; private set; }


    public MyWorkspace(string solutionFile) {
        this.SolutionFile = solutionFile;
        this.loadedWorkSpace = Workspace.LoadSolution(SolutionFile);
    }

    public void InjectTrace()
    {

        int projectCtr = 0;
        int documentsCtr = 0;
        int transformedMembers = 0;
        int transformedClasses = 0;
        this.CurrentSolution = this.LoadedWorkSpace.CurrentSolution;
        this.NewSolution = this.CurrentSolution;

        //For Each Project...
        foreach (var projectId in LoadedWorkSpace.CurrentSolution.ProjectIds)
        {
            CurrentProject = NewSolution.GetProject(projectId);

            //..for each Document in the Project..
            foreach (var docId in CurrentProject.DocumentIds)
            {
                CurrentDocument = NewSolution.GetDocument(docId);
                var docRoot = CurrentDocument.GetSyntaxRoot();
                var newDocRoot = docRoot;
                var classes = docRoot.DescendantNodes().OfType<ClassDeclarationSyntax>();
                IDocument newDocument = null;

                //..for each Class in the Document..
                foreach (var @class in classes) {
                    var methods = @class.Members.OfType<MethodDeclarationSyntax>();

                    //..for each Member in the Class..
                    foreach (var currMethod in methods) {
                        //..insert a Trace Statement
                        var newMethod = InsertTrace(currMethod);
                        transformedMembers++;
                        //TODO: PROBLEM IS HERE
                        newDocRoot = newDocRoot.ReplaceNode(currMethod, newMethod);                             
                    }
                    if (transformedMembers != 0) {
                        newDocument = CurrentDocument.UpdateSyntaxRoot(newDocRoot);
                        transformedMembers = 0;
                        transformedClasses++;
                    }
                }

                if (transformedClasses != 0) {
                    NewSolution = NewSolution.UpdateDocument(newDocument);
                    transformedClasses = 0;
                }

                documentsCtr++;

            }
            projectCtr++;
            if (projectCtr > 2) return;
        }
    }

    public MethodDeclarationSyntax InsertTrace(MethodDeclarationSyntax currMethod) {
        var traceText =
        @"System.Diagnostics.Trace.WriteLine(""Tracing: '" + currMethod.Ancestors().OfType<NamespaceDeclarationSyntax>().Single().Name + "." + currMethod.Identifier.ValueText + "'\");";
        var traceStatement = Syntax.ParseStatement(traceText);
        var bodyStatementsWithTrace = currMethod.Body.Statements.Insert(0, traceStatement);
        var newBody = currMethod.Body.Update(Syntax.Token(SyntaxKind.OpenBraceToken), bodyStatementsWithTrace,
                                            Syntax.Token(SyntaxKind.CloseBraceToken));
        var newMethod = currMethod.ReplaceNode(currMethod.Body, newBody);
        return newMethod;

    }

    public void ApplyChanges() {
        LoadedWorkSpace.ApplyChanges(CurrentSolution, NewSolution);
    }


}

最佳答案

你的代码的根本问题是 newDocRoot = newDocRoot.ReplaceNode(currMethod, newMethod); 以某种方式重建代码的 newDocRoot 内部表示,所以接下来 currMethod 元素不会在其中找到,接下来的 ReplaceNode 调用将不执行任何操作。这种情况类似于在其 foreach 循环中修改集合。

解决方案是收集所有必要的更改并使用 ReplaceNodes 方法立即应用它们。这实际上自然会导致代码的简化,因为我们不需要跟踪所有这些计数器。我们只需存储所有需要的转换并立即将它们应用于整个文档。

更改后的工作代码:

public void InjectTrace()
{
    this.CurrentSolution = this.LoadedWorkSpace.CurrentSolution;
    this.NewSolution = this.CurrentSolution;

    //For Each Project...
    foreach (var projectId in LoadedWorkSpace.CurrentSolution.ProjectIds)
    {
        CurrentProject = NewSolution.GetProject(projectId);
        //..for each Document in the Project..
        foreach (var docId in CurrentProject.DocumentIds)
        {
            var dict = new Dictionary<CommonSyntaxNode, CommonSyntaxNode>();
            CurrentDocument = NewSolution.GetDocument(docId);
            var docRoot = CurrentDocument.GetSyntaxRoot();
            var classes = docRoot.DescendantNodes().OfType<ClassDeclarationSyntax>();

            //..for each Class in the Document..
            foreach (var @class in classes)
            {
                var methods = @class.Members.OfType<MethodDeclarationSyntax>();

                //..for each Member in the Class..
                foreach (var currMethod in methods)
                {
                    //..insert a Trace Statement
                    dict.Add(currMethod, InsertTrace(currMethod));
                }
            }

            if (dict.Any())
            {
                var newDocRoot = docRoot.ReplaceNodes(dict.Keys, (n1, n2) => dict[n1]);
                var newDocument = CurrentDocument.UpdateSyntaxRoot(newDocRoot);
                NewSolution = NewSolution.UpdateDocument(newDocument);
            }
        }
    }
}

关于c# - 使用 Roslyn 替换方法节点,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20155266/

相关文章:

c# - 验证日期时间是按月顺序的

c# - Converge Payment Api 的测试信用卡号是多少?

c# - 在 Roslyn 制作的编译中包含嵌入式资源

c# Roslyn 从 ArgumentSyntax 中获取 IParameterSymbol

visual-studio-2015 - 在哪里可以找到 VS 2015 的 Roslyn 项目模板?

c# - 带有 ViewModel 绑定(bind)的 WPF ContentControl 不会改变(MVVM Light)

c# - 使用 BasicHttpBinding 进行身份验证的 WCF 服务

c# - 返回类中的所有字段

visual-studio - 作为重构的一部分,将新项目添加到解决方案中

c# - 如何使用 Roslyn 代码修复提供程序 API 从文档中删除 SyntaxNode 列表?