评估数学表达式的最佳和最短方法

发布于 2024-08-05 21:02:09 字数 386 浏览 10 评论 0原文

有许多算法可以计算表达式,例如:

  1. 通过递归下降
  2. 调车场算法
  3. 逆波兰表示法

有没有办法使用 C# .net 反射或其他现代 .net 技术来计算任何数学表达式?

There are many algorithms to evaluate expressions, for example:

  1. By Recursive Descent
  2. Shunting-yard algorithm
  3. Reverse Polish notation

Is there any way to evaluate any mathematical expression using C# .net reflection or other modern .net technology?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

郁金香雨 2024-08-12 21:02:09

根据 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();

Further to Thomas's answer, it's actually possible to access the (deprecated) JScript libraries directly from C#, which means you can use the equivalent of JScript's eval function.

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();
随梦而飞# 2024-08-12 21:02:09

这当然是可能的。 CodeSnippetCompileUnit 类基本上执行此操作。
我给你写了一些示例使用代码。您需要包含以下命名空间:

  • System.CodeDom.Compiler;
  • 系统.CodeDom;
  • 微软.CSharp;
  • 系统.反射;

代码如下:

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 很适合一次生成整个工作类(对于示例来说更简单),但要操作它,您将不得不进行不方便的字符串操作。

It's certainly possible. The CodeSnippetCompileUnit class does basically this.
I wrote you some example usage code. You'll need to include these namespaces:

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

Here's the code:

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 });

Replace 'parameters' and 'expression' by whatever, and you've got yourself a general expression evaluator.

If you get a FileNotFoundException in results.CompiledAssembly, then the snippet failed to compile.

You might also want to take a look at the System.CodeDom.CodeSnippetExpression class. It's used for more specifically reading expressions, but an expression by itself can't be compiled, so you would need to use more CodeDom to build a working class and method around it. This is useful if you want to be able to programmatically manipulate what kind of class you're generating. CodeSnippetCompileUnit is nice to generate an entire working class at once (and simpler for an example) but to manipulate it you would have to do inconvenient string manipulations.

蓝眸 2024-08-12 21:02:09

ncalc 是最好的。您可以在 codeplex 中找到它,也可以在 nugget 中找到它。
NCalc 是 .NET 中的数学表达式计算器。 NCalc 可以解析任何表达式并计算结果,包括静态或动态参数以及自定义函数。

ncalc is the best. you can find it in codeplex also in nugget.
NCalc is a mathematical expressions evaluator in .NET. NCalc can parse any expression and evaluate the result, including static or dynamic parameters and custom functions.

假情假意假温柔 2024-08-12 21:02:09

尽管使用编译器服务是一种简单而有效的解决方案,但如果用户输入表达式,则会引发严重的安全问题,因为它实际上可以执行任何东西

还有另一个非常简单但更安全的解决方案:利用 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);

Although using compiler services is a simple and efficient solution, it raises serious security issues if the expression is entered by a user, because it could execute virtually anything.

There's another very simple solution that is much more secure : take advantage of the JScript Eval function. You just need to follow these steps :

Create a js file named JsMath.js :

class JsMath
{
    static function Eval(expression : String) : double
    {
        return eval(expression);
    };
}

Compile it into a class library :

jsc /t:library JsMath.js

Reference the JsMath library in your C# project, and use it like that :

double result = JsMath.Eval(expression);
尝蛊 2024-08-12 21:02:09

对我来说,Vici.Parser 工作得非常好:在这里查看,它是最灵活的到目前为止我找到的表达式解析器。

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

提供了示例,并且开发人员提供了非常好的支持(查看网站的论坛)。

For me Vici.Parser works extremely well: check it out here , it's the most flexible expression parser I've found so far.

(we've used it to set up 'human-readable' business rules, with data provided by an SQL server database)

Examples are available and there's a very good support by the developer (check the website's forum).

千笙结 2024-08-12 21:02:09

我认为这是最好的方法。 Petar Repac 的回答令人惊叹。
使用 DataColumn 对象的“表达式”参数可以非常轻松地解决该主题:

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"]);
}

I think this is the best way of all. Petar Repac's answer is amazing.
Using the 'expression' argument of the DataColumn object solves incredibly and easily the topic:

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"]);
}
风渺 2024-08-12 21:02:09

您可以使用 Math-Expression-Evaluator 库,它实现了我作者的 Shunting Yard 算法的。支持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 项目中使用,并且没有任何外部依赖项。

You can use Math-Expression-Evaluator library which implements Shunting Yard algorithm that I am author of. It supports simple expressions such as 2.5+5.9, 17.89-2.47+7.16, 5/2/2+1.5*3+4.58, expressions with parentheses (((9-6/2)*2-4)/2-6-1)/(2+24/(2+4)) and expressions with variables:

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});

You can also pass parameters as named variables:

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);

It supports .Net Standard 2.0 so can be used from .Net Core as well as .Net Full Framework projects and it doesn't have any external dependencies.

笑着哭最痛 2024-08-12 21:02:09

使用新的 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

To dynamically compile code using the new Roslyn API's, and load the assembly in a .net core project;

    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);

Note that along with any other types required by the source you are compiling. In order to load the compiled assembly within the same process, references will need to include;

    AppDomain.CurrentDomain.GetAssemblies().Where(a => a.GetName().Name == "netstandard").Single(),
    typeof(object).Assembly
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文