在运行时编译 C# 代码以评估数学表达式






4.70/5 (47投票s)
2003年4月22日
2分钟阅读

222554
一种全新的数学表达式评估器实现方式。
引言
有很多文章介绍如何创建数学表达式解析器(例如,用于显示 3D 函数,用户手动输入公式)。大多数都是基于词法分析、语法定义等。使用 C#,有一种全新的方法来实现数学表达式评估器:在运行时编译代码。
假设我们想要创建一个能够计算如下 3D 函数的应用程序:
z = f(x,y)
但我们希望让用户手动输入函数公式(我们不知道公式会是什么样的)。
解决这个任务的方案,只需 10 分钟:
- 让用户输入函数
- 将该函数编译到内存中
- 使用给定的 x 和 y 运行该函数
- 对结果 z 值进行处理
为了在几分钟内完成这项工作,我们需要一个带有虚拟方法的类
namespace MathEval
{
public class MyClassBase
{
public MyClassBase()
{
}
public virtual double eval(double x,double y)
{
return 0.0;
}
}
}
我们还需要一个解析器/评估器类,它会将函数体编译成程序集(在内存中创建),并计算函数结果
using System;
using System.Reflection;
using System.Windows.Forms;
namespace MathEval
{
public class MathExpressionParser
{
MyClassBase myobj = null;
public MathExpressionParser()
{
}
public bool init(string expr)
{
Microsoft.CSharp.CSharpCodeProvider cp
= new Microsoft.CSharp.CSharpCodeProvider();
System.CodeDom.Compiler.ICodeCompiler ic = cp.CreateCompiler();
System.CodeDom.Compiler.CompilerParameters cpar
= new System.CodeDom.Compiler.CompilerParameters();
cpar.GenerateInMemory = true;
cpar.GenerateExecutable = false;
cpar.ReferencedAssemblies.Add("system.dll");
cpar.ReferencedAssemblies.Add("matheval.exe");
string src = "using System;"+
"class myclass:MathEval.MyClassBase" +
"{"+
"public myclass(){}"+
"public override double eval(double x,double y)"+
"{"+
"return "+ expr +";"+
"}"+
"}";
System.CodeDom.Compiler.CompilerResults cr
= ic.CompileAssemblyFromSource(cpar,src);
foreach (System.CodeDom.Compiler.CompilerError ce in cr.Errors)
MessageBox.Show(ce.ErrorText);
if (cr.Errors.Count == 0 && cr.CompiledAssembly != null)
{
Type ObjType = cr.CompiledAssembly.GetType("myclass");
try
{
if (ObjType != null)
{
myobj = (MyClassBase)Activator.CreateInstance(ObjType);
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
return true;
}
else
return false;
}
public double eval(double x,double y)
{
double val = 0.0;
if (myobj != null)
{
val = myobj.eval(x,y);
}
return val;
}
}
}
请查看 init() 方法。我们需要做的就是创建一个程序集(在内存中),其中包含从 MyBaseClass 派生的“myclass”类。请注意,“eval”方法包含用户在运行时提供的公式。 另外请注意,cpar.ReferencedAssemblies.Add("matheval.exe"); 定义了对当前正在创建的应用程序的引用。
然后我们可以根据需要多次调用 myclass.eval 方法
MathExpressionParser mp = new MathExpressionParser();
mp.init("Math.Sin(x)*y");
for(int i=0;i<1000000;i++)
{
mp.eval((double)i,(double)i);
}
编译后的代码当然比任何其他用于解析和评估表达式的解决方案都要快得多。
还有一些事情要做:
- 让用户输入诸如“xy*sin(x+y)”或“x^2+y^2”之类的公式,并将它们转换为 C# “x*y*Math.Sin(x+y)” “x*x + y*y” 等…
- 进行一些错误检查/处理,而不仅仅是编译器错误。
- 释放并使用另一个公式重新创建内存中的程序集。
- 享受代码吧 :)
PS:特别感谢 AcidRain 提供的一些想法 :)