【问题标题】:Is it possible to compile and execute new code at runtime in .NET?.NET 中是否可以在运行时编译和执行新代码?
【发布时间】:2010-09-19 01:15:15
【问题描述】:

注意:数学表达式求值不是本题的重点。我想在 .NET 中在运行时编译和执行新代码。 话虽这么说......

我想允许用户在文本框中输入任何公式,如下所示:

x = x / 2 * 0.07914
x = x^2 / 5

并将该等式应用于传入的数据点。传入的数据点由 x 表示,每个数据点由用户指定的方程处理。几年前我做过,但我不喜欢这个解决方案,因为它需要为每次计算解析方程的文本:

float ApplyEquation (string equation, float dataPoint)
{
    // parse the equation string and figure out how to do the math
    // lots of messy code here...
}

当您处理大量数据点时,这会带来相当多的开销。我希望能够动态地将方程转换为一个函数,这样它只需要解析一次。它看起来像这样:

FunctionPointer foo = ConvertEquationToCode(equation);
....
x = foo(x);  // I could then apply the equation to my incoming data like this

Function ConvertEquationToCode 将解析方程并返回一个指向应用适当数学的函数的指针。

该应用程序基本上会在运行时编写新代码。 .NET 可以做到这一点吗?

【问题讨论】:

标签: c# .net compilation runtime


【解决方案1】:

是的!使用在 Microsoft.CSharp、System.CodeDom.Compiler 和 System.Reflection 名称空间中找到的方法。这是一个简单的控制台应用程序,它使用一种方法(“Add42”)编译一个类(“SomeClass”),然后允许您调用该方法。这是一个简单的示例,我将其格式化以防止滚动条出现在代码显示中。这只是为了演示在运行时编译和使用新代码。

using Microsoft.CSharp;
using System;
using System.CodeDom.Compiler;
using System.Reflection;

namespace RuntimeCompilationTest {
    class Program
    {
        static void Main(string[] args) {
            string sourceCode = @"
                public class SomeClass {
                    public int Add42 (int parameter) {
                        return parameter += 42;
                    }
                }";
            var compParms = new CompilerParameters{
                GenerateExecutable = false, 
                GenerateInMemory = true
            };
            var csProvider = new CSharpCodeProvider();
            CompilerResults compilerResults = 
                csProvider.CompileAssemblyFromSource(compParms, sourceCode);
            object typeInstance = 
                compilerResults.CompiledAssembly.CreateInstance("SomeClass");
            MethodInfo mi = typeInstance.GetType().GetMethod("Add42");
            int methodOutput = 
                (int)mi.Invoke(typeInstance, new object[] { 1 }); 
            Console.WriteLine(methodOutput);
            Console.ReadLine();
        }
    }
}

【讨论】:

  • 这对于简单的算术表达式来说非常慢。
  • @JonathanNappee:我很高兴发现如何做到这一点,我并没有说它很棒。我很想看到您的性能改进!
  • @Jonathan Nappee,你能解释一下你的评论吗?我想验证这种添加“Add42”的方法并不比将其内置到程序中慢。换句话说,您的评论更多地针对“添加 42 的方法效率低下”,而不是“这种在运行时向程序添加代码的方法效率低下”,对吗?
  • @Derek 非常慢的部分是需要编译、创建实例和运行 Invoke。这可能是几百毫秒的开销。一个简单的算术解析器可能会快一个数量级。
  • @Jonathan Nappee 感谢您的解释。为了确保我了解如何最好地使用(或不使用)它:如果您可以缓存编译结果并多次重用它,它可能会抵消所述编译的开销成本。此外,与简单的算术相比,复杂的任务更适合这种方法,对于简单的算术,你可能最好使用 Calculator.Net 之类的东西。这两种说法都是真的吗?
【解决方案2】:

你可以试试这个:Calculator.Net

它将计算一个数学表达式。

从发帖来看,它将支持以下内容:

MathEvaluator eval = new MathEvaluator();
//basic math
double result = eval.Evaluate("(2 + 1) * (1 + 2)");
//calling a function
result = eval.Evaluate("sqrt(4)");
//evaluate trigonometric 
result = eval.Evaluate("cos(pi * 45 / 180.0)");
//convert inches to feet
result = eval.Evaluate("12 [in->ft]");
//use variable
result = eval.Evaluate("answer * 10");
//add variable
eval.Variables.Add("x", 10);            
result = eval.Evaluate("x * 10");

Download Page 并且在 BSD 许可下分发。

【讨论】:

  • 你会想看看 CodeDom 命名空间,它可以在运行时编译代码。
  • 这个库似乎有一些错误,似乎仍然不受支持。
【解决方案3】:

是的,绝对可以让用户在文本框中键入 C#,然后编译该代码并在您的应用程序中运行它。我们在我的工作中这样做是为了允许自定义业务逻辑。

这是一篇文章(我只是略读了一下),它应该可以帮助您入门:

http://www.c-sharpcorner.com/UploadFile/ChrisBlake/RunTimeCompiler12052005045037AM/RunTimeCompiler.aspx

【讨论】:

    【解决方案4】:

    您还可以从空的“虚拟”XML 流创建 System.Xml.XPath.XPathNavigator, 并使用 XPath 评估器评估表达式:

    static object Evaluate ( string xp )
    {
      return _nav.Evaluate ( xp );
    }
    static readonly System.Xml.XPath.XPathNavigator _nav
      = new System.Xml.XPath.XPathDocument (
          new StringReader ( "<r/>" ) ).CreateNavigator ( );
    

    如果你想注册变量在这个表达式中使用, 您可以动态构建可以在 Evaluate 重载中传递的 XML 这需要一个 XPathNodeIterator。

    <context>
      <x>2.151</x>
      <y>231.2</y>
    </context>
    

    然后您可以编写诸如“x / 2 * 0.07914”之类的表达式,然后是 x 是 XML 上下文中节点的值。 另一件好事是,您将可以访问所有 XPath 核心功能, 其中包括数学和字符串操作方法等等。

    如果您想更进一步,您甚至可以构建自己的 XsltCustomContext(或按需在此处发布) 您可以在其中解析对扩展函数和变量的引用:

    object result = Evaluate ( "my:func(234) * $myvar" );
    

    my:func 映射到 C#/.NET 方法,该方法采用 double 或 int 作为参数。 myvar 在 XSLT 上下文中注册为变量。

    【讨论】:

      【解决方案5】:

      您可以尝试查看 CodeDom 或 Lambda 表达式树。我认为其中任何一个都应该允许您完成此任务。表达树可能是更好的方法,但也有更高的学习曲线。

      【讨论】:

        【解决方案6】:

        我已经使用 CSharpCodeProvider 通过在生成器类中创建样板类和函数内容作为 const 字符串来完成此操作。然后我将用户代码插入样板并编译。

        这样做相当简单,但这种方法的危险在于,输入等式的用户可能会输入几乎任何可能存在安全问题的内容,具体取决于您的应用程序。

        如果完全考虑安全性,我建议使用 Lambda 表达式树,但如果不是,使用 CSharpCodeProvider 是一个相当可靠的选择。

        【讨论】:

          【解决方案7】:

          你见过http://ncalc.codeplex.com 吗?

          它是可扩展的、快速的(例如有自己的缓存),使您能够通过处理 EvaluateFunction/EvaluateParameter 事件在运行时提供自定义函数和变量。它可以解析的示例表达式:

          Expression e = new Expression("Round(Pow(Pi, 2) + Pow([Pi2], 2) + X, 2)"); 
          
            e.Parameters["Pi2"] = new Expression("Pi * Pi"); 
            e.Parameters["X"] = 10; 
          
            e.EvaluateParameter += delegate(string name, ParameterArgs args) 
              { 
                if (name == "Pi") 
                args.Result = 3.14; 
              }; 
          
            Debug.Assert(117.07 == e.Evaluate()); 
          

          它还可以原生处理 unicode 和许多数据类型。如果您想更改语法,它会附带一个 antler 文件。还有一个 fork 支持 MEF 加载新功能。

          【讨论】:

            【解决方案8】:

            您可以从here 开始,如果您真的想进入它,可以修改Boo 以满足您的需求。您还可以集成LUA with .NET。其中任何三个都可以在您的ConvertEquationToCode 的代表体内使用。

            【讨论】:

              【解决方案9】:

              试试 Vici.Parser: download it here (free) ,它是迄今为止我发现的最灵活的表达式解析器/求值器。

              【讨论】:

                【解决方案10】:

                这里有一个更现代的简单表达式库:System.Linq.Dynamic.Core。 兼容.NET Standard/.NET Core,可通过NuGet获取,源码可查。

                https://system-linq-dynamic-core.azurewebsites.net/html/de47654c-7ae4-9302-3061-ea6307706cb8.htm https://github.com/StefH/System.Linq.Dynamic.Core https://www.nuget.org/packages/System.Linq.Dynamic.Core/

                这是一个非常轻量级的动态库。

                我为这个库写了一个简单的包装类,让我可以做这样的事情:

                  string sExpression = "(a == 0) ? 5 : 10";
                  ExpressionEvaluator<int> exec = new ExpressionEvaluator<int>(sExpression);
                  exec.AddParameter("a", 0);
                  int n0 = exec.Invoke();
                

                编译表达式后,您可以简单地更新参数值并重新调用表达式。

                【讨论】:

                  【解决方案11】:

                  如果一切都失败了,System.Reflection.Emit 命名空间下的类可以用来生成新的程序集、类和方法。

                  【讨论】:

                  • 请注意。我们在 .Less (dotlesscss.com) 上使用了它,而发出代码通常意味着糟糕的性能。
                  【解决方案12】:

                  您可以实现postfix stack calculator。基本上,您要做的就是将表达式转换为后缀表示法,然后简单地遍历后缀中的标记进行计算。

                  【讨论】:

                    【解决方案13】:

                    我会做一个递归函数,它不编写代码,而是根据字符串中的特殊字符将基本运算符应用于字符串的某些部分。 如果找到多个特殊字符,它会分解字符串并在这两部分上调用自身。

                    【讨论】:

                    • 你想要的是一个表达式解析器。我通过将表达式从中缀转换为后缀然后使用基于堆栈的 posfix 解析器来做到这一点。这两种算法的定义都很好,并且存在于网络和书籍中。
                    【解决方案14】:

                    您可以使用system.CodeDom 生成代码并即时编译它 看看here

                    【讨论】:

                      【解决方案15】:

                      我不知道是否可以实现您的ConvertEquationToCode 函数,但是,您可以生成一个表示您需要执行的计算的数据结构。

                      例如,您可以构建一棵树,其叶节点代表计算的输入,其非叶节点代表中间结果,其根节点代表整个计算。

                      它有一些优点。例如,如果您正在进行假设分析并希望一次更改一个输入的值,您可以根据已更改的值重新计算结果,同时保留未更改的结果。

                      【讨论】:

                        猜你喜欢
                        • 1970-01-01
                        • 1970-01-01
                        • 2016-09-28
                        • 2013-01-05
                        • 2010-10-24
                        • 1970-01-01
                        相关资源
                        最近更新 更多