评估数学表达式的最佳且最短的方法是什么?

18

我之前问过一个类似的问题。你可能想看看那些答案:https://dev59.com/nnVC5IYBdhLWcg3woCnN - raven
你找到了一种方法来链接到其余的“静态/预编译”代码中使用的变量吗? - Daniel Mošmondor
8个回答

19

在Thomas的回答之后,实际上可以直接从C#访问(已弃用的)JScript库,这意味着您可以使用类似于JScript的eval函数的等效项。

using Microsoft.JScript;        // needs a reference to Microsoft.JScript.dll
using Microsoft.JScript.Vsa;    // needs a reference to Microsoft.Vsa.dll

// ...

string expr = "7 + (5 * 4)";
Console.WriteLine(JScriptEval(expr));    // displays 27

// ...

public static double JScriptEval(string expr)
{
    // error checking etc removed for brevity
    return double.Parse(Eval.JScriptEvaluate(expr, _engine).ToString());
}

private static readonly VsaEngine _engine = VsaEngine.CreateEngine();

很遗憾它不支持插入符号^进行指数运算。 - Dan W

13

这是完全可能的。 CodeSnippetCompileUnit 类基本上就是这样实现的。我为您编写了一些示例代码,您需要包含以下命名空间:

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

这是代码:

string source = @"
class MyType
{
    public static int Evaluate(<!parameters!>)
    {
        return <!expression!>;
    }
}
";

string parameters = "int a, int b, int c";
string expression = "a + b * c";

string finalSource = source.Replace("<!parameters!>", parameters).Replace("<!expression!>", expression);

CodeSnippetCompileUnit compileUnit = new CodeSnippetCompileUnit(finalSource);
CodeDomProvider provider = new CSharpCodeProvider();

CompilerParameters parameters = new CompilerParameters();

CompilerResults results = provider.CompileAssemblyFromDom(parameters, compileUnit);

Type type = results.CompiledAssembly.GetType("MyType");
MethodInfo method = type.GetMethod("Evaluate");

// The first parameter is the instance to invoke the method on. Because our Evaluate method is static, we pass null.
int result = (int)method.Invoke(null, new object[] { 4, -3, 2 });

使用任何参数和表达式,您都可以创建一个通用的表达式求值器。

如果在results.CompiledAssembly中出现FileNotFoundException,则表示代码片段编译失败。

您可能还想查看System.CodeDom.CodeSnippetExpression类。它用于更具体地读取表达式,但是单独的表达式无法编译,因此您需要使用更多的CodeDom来构建一个可工作的类和方法。如果您希望能够以编程方式操作正在生成的类的类型,则这很有用。CodeSnippetCompileUnit非常适合一次生成整个工作类(也更简单),但要对其进行操作,则需要进行不方便的字符串操作。


值得一提的是,与使用ncalc相比,这种解决方案的性能差距巨大。我为一个绘图器进行了测试,一些多变量函数需要超过500秒才能绘制完成,但使用这个解决方案,我只需要不到5秒就可以绘制超过400,000个点。非常棒的解决方案! - Alejandro Cortes

4

NCalc 是最好的数学表达式计算器,在 CodePlex 和 Nugget 上都可以找到。
NCalc 是 .NET 平台上的数学表达式计算器。它可以解析任何表达式并计算结果,包括静态或动态参数和自定义函数。


3
尽管使用编译器服务是一种简单高效的解决方案,但如果用户输入表达式,则会引发严重的安全问题,因为它可以执行几乎任何内容。
还有另一个非常简单的解决方案,更加安全:利用JScript的Eval函数。只需按照以下步骤操作:
创建名为JsMath.js的js文件:
class JsMath
{
    static function Eval(expression : String) : double
    {
        return eval(expression);
    };
}

将其编译成类库:

jsc /t:library JsMath.js

在您的C#项目中引用JsMath库,并像这样使用它:
double result = JsMath.Eval(expression);

实际上,可以直接从C#访问eval函数,而无需中间的JScript编译步骤。有关详细信息,请参见我的答案。 - LukeH
为了避免使用编译器服务时出现安全问题,我使用 ANTL 预解析用户表达式并避免任何奇怪的输入。如果您正在寻求性能,eval() 函数可能无法正常工作。 - Alejandro Cortes

3

对我来说,Vici.Parser非常好用:在这里查看,它是我目前发现的最灵活的表达式解析器。

(我们使用它来设置“可读性强”的业务规则,并使用SQL服务器数据库提供数据)

有示例可用,并且开发者提供了非常好的支持(请查看网站论坛)。


1
@Roel - 链接已失效。 - Mark Lopez

1
我认为这是最好的方法。Petar Repac的答案非常棒。 使用DataColumn对象的“expression”参数可以令这个主题变得非常容易和高效解决:
static double Evaluate(string expression)
{
    var loDataTable = new DataTable();
    var loDataColumn = new DataColumn("Eval", typeof(double), expression);
    loDataTable.Columns.Add(loDataColumn);
    loDataTable.Rows.Add(0);
    return (double)(loDataTable.Rows[0]["Eval"]);
}

0
使用新的Roslyn API动态编译代码,并在.NET Core项目中加载程序集;
    string finalSource = ...;
    IEnumerable<Assembly> references = ...;

    var compilation = CSharpCompilation.Create("Dynamic",
        new[] { 
            SyntaxFactory.ParseSyntaxTree(
                finalSource,
                CSharpParseOptions.Default
                    .WithLanguageVersion(LanguageVersion.Latest)
            ) },
        references.Select(a => MetadataReference.CreateFromFile(a.Location)),

        new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
            .WithAssemblyIdentityComparer(DesktopAssemblyIdentityComparer.Default)
    );

    using var ms = new MemoryStream();
    var e = compilation.Emit(ms);
    if (!e.Success)
        throw new Exception("Compilation failed");
    ms.Seek(0, SeekOrigin.Begin);

    var context = new AssemblyLoadContext(null, true);
    var assembly = context.LoadFromStream(ms);

请注意,除了编译源代码所需的任何其他类型之外。为了在同一进程中加载已编译的程序集,引用将需要包括;
    AppDomain.CurrentDomain.GetAssemblies().Where(a => a.GetName().Name == "netstandard").Single(),
    typeof(object).Assembly

0
你可以使用我编写的Shunting Yard算法实现的Math-Expression-Evaluator库。它支持简单的表达式,例如2.5+5.917.89-2.47+7.165/2/2+1.5*3+4.58,带有括号的表达式(((9-6/2)*2-4)/2-6-1)/(2+24/(2+4))以及带有变量的表达式:
var a = 6;
var b = 4.32m;
var c = 24.15m;
var engine = new ExpressionEvaluator();
engine.Evaluate("(((9-a/2)*2-b)/2-a-1)/(2+c/(2+4))", new { a, b, c});

你也可以将参数作为命名变量传递:

dynamic dynamicEngine = new ExpressionEvaluator();

var a = 6;
var b = 4.5m;
var c = 2.6m;

dynamicEngine.Evaluate("(c+b)*a", a: 6, b: 4.5, c: 2.6);

它支持 .Net Standard 2.0,因此可以从 .Net Core 以及 .Net Full Framework 项目中使用,并且不需要任何外部依赖。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接