35

注意:数学表达式评估不是本题的重点。我想在.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

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

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

4

15 回答 15

19

是的!使用Microsoft.CSharpSystem.CodeDom.CompilerSystem.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();
        }
    }
}
于 2012-05-04T23:12:05.990 回答
13

你可以试试这个: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");

下载页面 并且在 BSD 许可下分发。

于 2008-10-24T16:39:21.630 回答
7

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

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

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

于 2008-10-24T16:24:04.530 回答
6

您还可以从空的“虚拟”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 ( );

如果要注册要在此表达式中使用的变量,您可以动态构建 XML,您可以将其传递到采用 XPathNodeIterator 的 Evaluate 重载中。

<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 上下文中注册为变量。

于 2008-10-24T18:12:15.357 回答
3

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

于 2008-10-24T16:21:30.340 回答
3

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

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

如果安全是一个问题,我会推荐使用 Lambda 表达式树,但如果不是,使用 CSharpCodeProvider 是一个相当强大的选项。

于 2008-10-24T16:54:45.497 回答
3

你看过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 加载新功能。

于 2011-08-10T15:07:40.427 回答
2

您可以从这里开始,如果您真的想进入它,可以修改Boo以满足您的需求。您还可以将LUA 与 .NET集成。其中任何三个都可以在您的ConvertEquationToCode.

于 2008-10-24T16:23:43.720 回答
1

试试 Vici.Parser:在这里下载(免费),它是迄今为止我发现的最灵活的表达式解析器/求值器。

于 2009-09-17T12:46:35.680 回答
1

这里有一个更现代的简单表达式库: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();

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

于 2018-05-20T01:41:24.570 回答
0

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

于 2008-10-24T19:56:48.500 回答
0

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

于 2010-02-04T00:58:00.663 回答
-1

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

于 2008-10-24T16:23:32.317 回答
-1

您可以使用system.CodeDom它来生成代码并即时编译,看看这里

于 2010-02-04T00:52:14.513 回答
-2

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

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

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

于 2008-12-17T19:29:02.947 回答