是否可以在运行时在 .NET 中编译和执行新代码

本文关键字:新代码 执行 代码 编译 运行时 NET 是否 | 更新日期: 2023-09-27 17:47:22

注意:数学表达式评估不是这个问题的重点。 我想在运行时在 .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 中可能吗?

是否可以在运行时在 .NET 中编译和执行新代码

是的! 使用在 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();
        }
    }
}
你可以

试试这个: 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("answerswer * 10");
//add variable
eval.Variables.Add("x", 10);            
result = eval.Evaluate("x * 10");

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

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

这是一篇文章(我只是略读了一下),应该让你开始:

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

您还可以从空的"虚拟"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。

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

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

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

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

如果安全性是一个问题,我建议使用 Lambda 表达式树,但如果没有,使用 CSharpCodeProvider 是一个相当强大的选择。

你见过 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和许多数据类型。如果您想更改克默,它带有一个鹿角文件。还有一个分支支持MEF加载新功能。

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

https://system-linq-dynamic-core.azurewebsites.net/html/de47654c-7ae4-9302-3061-ea6307706cb8.htmhttps://github.com/StefH/System.Linq.Dynamic.Corehttps://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();

编译表达式后,只需更新参数值并重新调用表达式即可。

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

尝试 Vici.Parser:在此处下载(免费),它是迄今为止我找到的最灵活的表达式解析器/计算器。

如果所有其他方法都失败了,则 System.Reflection.Emit 命名空间下有一些类,您可以使用这些类来生成新的程序集、类和方法。

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

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

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

我不知道

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

例如,可以生成一个树,其叶节点表示计算

的输入,其非叶节点表示中间结果,其根节点表示整个计算。

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