问题 解析C#代码(作为字符串)并插入其他方法


我有一个我正在研究的C#应用​​程序远程加载它的代码,然后运行它(为了争论,你可以假设应用程序是安全的)。

代码是C#,但它作为XML文档发送,解析为字符串,然后编译和执行。

现在,我想做的事情 - 并且比我预期的要困难得多 - 能够解析整个文档,并且在编译之前,在每行执行之后插入其他命令。

例如,考虑代码:

using System;
using System.Collections.Generic;
using System.Linq;

namespace MyCode
{
    static class MyProg
    {
        static void Run()
        {
            int i = 0;
            i++;

            Log(i);
        }
    }
}

解析后我想要的更像是:

using System;
using System.Collections.Generic;
using System.Linq;

namespace MyCode
{
    static class MyProg
    {
        static void Run()
        {
            int i = 0;
            MyAdditionalMethod();
            i++;
            MyAdditionalMethod();

            Log(i);
            MyAdditionalMethod();
        }
    }
}

请记住明显的陷阱 - 我不能在每个分号后都有它,因为这在吸气剂/定位器中不起作用,即:

转换:

public string MyString { get; set; }

至:

public string MyString { get; MyAdditionalMethod(); set; MyAdditionalMethod(); }

会失败的。与类级声明,使用语句等一样。此外,在许多情况下我也可以在花括号后添加MyAdditionalMethod() - 比如在委托中,在if语句之后,或方法声明等。

所以,我一直在研究CodeDOM,看起来它可能是一个解决方案,但很难弄清楚从哪里开始。我试图解析整个事情并创建一个我可以解析的树 - 虽然这有点难,考虑到我需要考虑的案例数量。

有谁知道其他任何解决方案?


9119
2018-02-14 23:39


起源

“代码是C#,但它是作为XML文档发送的,以字符串形式解析” - 除了这些听起来有多奇怪之外,我们能看到XML的样本吗?也许您可以在将方法解析为字符串之前注入它们。 - mpen
我不控制XML文档,所以我不能注入任何东西,直到它到达并被解析出来。 XML只包含代码作为元素的内部文本 - 即。 <Code>使用System.IO;名称空间MyApp {public class myClass {... </ Code> - AlishahNovin


答案:


有一些C#解析器,我建议使用Mono或SharpDevelop的东西,因为它们应该是最新的。我去玩了 NRefactory 来自SharpDevelop,如果你 下载 SharpDevelop的源代码有一个演示和一些UnitTests,它们是一个很好的介绍它的用法。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ICSharpCode.NRefactory;
using System.IO;
using ICSharpCode.NRefactory.Ast;
using ICSharpCode.NRefactory.Visitors;
using ICSharpCode.NRefactory.PrettyPrinter;

namespace Parse
{
    class Program
    {
        static void Main(string[] args)
        {
            string code = @"using System;
            using System.Collections.Generic;
            using System.Linq;

            namespace MyCode
            {
                static class MyProg
                {
                    static void Run()
                    {
                        int i = 0;
                        i++;

                        Log(i);
                    }
                }
            }
            ";

            IParser p = ParserFactory.CreateParser(SupportedLanguage.CSharp, new StringReader(code));
            p.Parse();

            //Output Original
            CSharpOutputVisitor output = new CSharpOutputVisitor();
            output.VisitCompilationUnit(p.CompilationUnit, null);
            Console.Write(output.Text);

            //Add custom method calls
            AddMethodVisitor v = new AddMethodVisitor();
            v.VisitCompilationUnit(p.CompilationUnit, null);
            v.AddMethodCalls();
            output = new CSharpOutputVisitor();
            output.VisitCompilationUnit(p.CompilationUnit, null);

            //Output result
            Console.Write(output.Text);
            Console.ReadLine();
        }


    }

    //The vistor adds method calls after visiting by storing the nodes in a dictionary. 
    public class AddMethodVisitor : ConvertVisitorBase
    {
        private IdentifierExpression member = new IdentifierExpression("MyAdditionalMethod");

        private Dictionary<INode, INode> expressions = new Dictionary<INode, INode>();

        private void AddNode(INode original)
        {
            expressions.Add(original, new ExpressionStatement(new InvocationExpression(member)));
        }

        public override object VisitExpressionStatement(ExpressionStatement expressionStatement, object data)
        {
            AddNode(expressionStatement);
            return base.VisitExpressionStatement(expressionStatement, data);
        }

        public override object VisitLocalVariableDeclaration(LocalVariableDeclaration localVariableDeclaration, object data)
        {
            AddNode(localVariableDeclaration);
            return base.VisitLocalVariableDeclaration(localVariableDeclaration, data);
        }

        public void AddMethodCalls()
        {
            foreach (var e in expressions)
            {
                InsertAfterSibling(e.Key, e.Value);
            }
        }

    }
}

您需要改进访问者以处理更多案例,但这是一个良好的开端。

或者你可以编译原文并使用一些IL操作 塞西尔 或尝试一些类似的AOP库 PostSharp。最后你可以看一下 .NET Profiling API


7
2018-02-27 20:38





您可以使用源到源程序转换系统。这样的工具解析代码,构建和AST,允许您应用转换,然后从AST重新生成文本。什么使源到源系统很好,你可以根据源语言语法而不是AST的分形细节来编写转换,这使得它们更容易在以后编写和理解。

您想要做的事情将通过一个非常简单的程序转换来建模 用我们的 DMS软件再造工具包

rule insert_post_statement_call(s: stmt): stmt -> stmt =
   " \s " -> " { \s ; MyAdditionalMethod();   }";

这条规则不是“文本”替代;相反,它由处理目标代码的解析器解析,因此实际上它代表两个AST,左侧和右侧(由“ - >”语法分隔。引号不是字符串引号;它们是目标语言语法的引用,以区别于规则语言本身的语法。引号内部是目标语言(例如,C#)带有转义符的文本,如\ s,表示整个语言元素(在本例中为stmt根据目标语言(例如C#)语法。左侧表示“匹配任何语句s”,因为s被定义为语法中的“stmt”。右侧表示“用一个语句替换语句”包含原始语句的块,以及要插入的新代码“。这都是使用语法作为指导语法树完成的;它不能将转换应用于任何不是语句的内容。[将语句重写为块的原因是因为这种方式是正确的  在语句有效的地方有效,去检查你的语法。]

实际上,您需要编写规则来处理其他特殊情况,但这主要是编写更多规则。您还需要将解析器/ transformer / prettyprinter打包为bundle,这需要一些程序粘合剂。这比尝试编写代码可靠地爬上树,匹配节点然后粉碎这些节点以获得您想要的东西要容易得多。更好的是,当你的语法(总是)必须调整时,重写规则会根据修改后的语法进行重新分析,并且仍然有效;无论你在做什么程序爬树,几乎可以肯定要打破。

随着您编写越来越多的转换,此功能变得越来越有价值。当您成功进行少量转换时,添加更多内容会很快变得有吸引力。

看到 这篇技术论文 有关DMS如何工作的更全面的讨论,以及它如何用于在实际工具中应用仪器转换,就像您想要的那样。本文描述了Semantic Designs销售的测试覆盖工具背后的基本思想。


3
2018-02-26 02:57



这似乎只是您可能不需要的产品的广告。 .Net框架为即时编译提供了简单的类 - TFD
@TFD:来自OP:“有没有人知道其他任何解决方案?”谢谢你的叮当声。 - Ira Baxter


你需要的是使用表达式树。来自MSDN的一些有用信息:


1
2018-02-14 23:55





对于解析,您可以使用 CSharpCodeProvider类的解析()。


1
2018-02-25 21:39



我认为这会引发NotImplementedException。 - Simon Mourier
@Simon:不正确。基类(CodeDomProvider)抛出NotImplementedException。派生类(例如CSharpCodeProvider)会覆盖该方法,因此不会抛出此类异常。 - Mike Hofer
@Mike - 你在哪里看到CSharpCodeProvider重写CreateParser或Parse? - Simon Mourier


解析文本后,此页面包含有关动态编译和执行代码的一些很好的信息: http://www.west-wind.com/presentations/dynamiccode/dynamiccode.htm


1
2018-02-25 23:13