是否有可用于评估对象表达式的现成组件?

时间:2012-03-07 13:59:40

标签: c#

我们想解析类型的表达式:

Func<T1, bool>Func<T1, T2, bool>Func<T1, T2, T3, bool>

我知道构建表达式树并对其进行评估相对容易,但我想避开在表达式树上进行编译的开销。

有没有可以做到这一点的现成组件?

是否有任何组件可以从字符串中解析C#表达式并对其进行求值? (对于C#的表达式服务,我认为WF4使用的是VB可用的东西)

修改 我们有特定的模型,我们需要在这些模型上评估IT管理员输入的表达式。

public class SiteModel
{
    public int NumberOfUsers {get;set;}
    public int AvailableLicenses {get;set;}
}

我们希望他们输入如下表达式:

Site.NumberOfUsers > 100 && Site.AvailableLicenses < Site.NumberOfUsers

然后我们想生成一个可以通过传递SiteModel对象来评估的Func。

Func<SiteModel, bool> (Site) => Site.NumberOfUsers > 100 && Site.AvailableLicenses < Site.NumberOfUsers

此外,性能不应该是悲惨的(但普通PC上每秒大约80-100个电话应该没问题。)

8 个答案:

答案 0 :(得分:1)

Mono.CSharp可以从字符串中计算表达式,并且使用起来非常简单。所需的引用随mono编译器和运行时一起提供。 (在工具目录中iirc)。

您需要引用Mono.CSharp.dll和Mono C#编译器可执行文件(mcs.exe)。

接下来设置评估者,以便在必要时了解您的代码。

using Mono.CSharp;
...
Evaluator.ReferenceAssembly (Assembly.GetExecutingAssembly ());
Evaluator.Run ("using Foo.Bar;");

然后评估表达式就像调用Evaluate一样简单。

var x = (bool) Evaluator.Evaluate ("0 == 1");

答案 1 :(得分:1)

也许ILCalc(在codeplex上)可以满足您的需求。它来自.NET和Silverlight版本,并且是开源的。

我们已成功使用它已有一段时间了。它甚至允许您引用表达式中的变量。

答案 2 :(得分:1)

也许这种技术对您有用 - 特别是关于依赖性审核,因为您完全依赖于框架组件。

编辑:正如@Asti所指出的,这种技术会创建动态程序集,遗憾的是,由于.net框架设计的限制,无法卸载,因此在使用它之前应该仔细考虑。这意味着如果更新了脚本,则无法从内存中卸载包含以前版本脚本的旧程序集,并且该程序集将一直延迟,直到重新启动托管它的应用程序或服务。

在脚本中的更改频率降低,以及编译的脚本被缓存和重用并且每次使用时都不重新编译的情况下,这个内存泄漏可以安全地容忍IMO(这已经是所有我们使用这种技术的情况)。幸运的是,根据我的经验,典型脚本生成的程序集的内存占用量往往很小。

如果这是不可接受的,那么脚本可以在可以从内存中删除的单独AppDomain上编译,但是,这需要在域之间进行调用封送(例如,命名管道WCF服务),或者可能是IIS托管服务,在不活动期后自动卸载,或超出内存占用阈值。)

结束编辑

首先,您需要向项目添加对Microsoft.CSharp的引用,并添加以下使用语句

using System.CodeDom.Compiler;   // this is included in System.Dll assembly
using Microsoft.CSharp;

然后,我正在添加以下方法:

 private void TestDynCompile() {
         // the code you want to dynamically compile, as a string

         string code = @"
            using System;

            namespace DynCode {
               public class TestClass {
                  public string MyMsg(string name) {
                     //---- this would be code your users provide
                     return string.Format(""Hello {0}!"", name);
                     //-----
                  }
               }
            }";

         // obtain a reference to a CSharp compiler
         var provider = CodeDomProvider.CreateProvider("CSharp");

         // Crate instance for compilation parameters
         var cp = new CompilerParameters();

         // Add assembly dependencies
         cp.ReferencedAssemblies.Add("System.dll");

         // hold compiled assembly in memory, don't produce an output file
         cp.GenerateInMemory = true;
         cp.GenerateExecutable = false;

         // don't produce debugging information    
         cp.IncludeDebugInformation = false;

         // Compile source code
         var rslts = provider.CompileAssemblyFromSource(cp, code);

         if( rslts.Errors.Count == 0 ) {
            // No errors in compilation, obtain type for DynCode.TestClass
            var type = rslts.CompiledAssembly.GetType("DynCode.TestClass");
            // Create an instance for the dynamically compiled class
            dynamic instance = Activator.CreateInstance(type);
            // Invoke dynamic code
            MessageBox.Show(instance.MyMsg("Gerardo"));  // Hello Gerardo! is diplayed  =)
         }
       }

正如您所看到的,您需要添加样板代码,如包装器类定义,注入程序集依赖项等),但这是一种非常强大的技术,可以使用完整的C#语法添加脚本功能,并且执行速度几乎与静态一样快码。 (调用会慢一点)。

程序集依赖项可以引用您自己的项目依赖项,因此可以在动态代码中引用和使用项目中定义的类和类型。

希望这有帮助!

答案 3 :(得分:1)

您正在谈论的“组件”:

  • 需要了解C#语法(用于解析输入字符串)
  • 需要了解C#语义(在哪里执行隐式int-&gt;双转换等)
  • 需要生成IL代码

这样的“组件”称为C#编译器。

  1. 当前的Microsoft C#编译器是一个糟糕的选项,因为它在一个单独的进程中运行(因此增加了编译时间,因为所有元数据都需要加载到该进程中)并且只能编译完整的程序集(并且.NET程序集不能卸载而不卸载整个AppDomain,从而泄漏内存)。但是,如果你能忍受这些限制,这是一个简单的解决方案 - 请参阅sgorozco的回答。

  2. 未来的Microsoft C#编译器(Roslyn项目)将能够做你想做的事情,但这仍然是未来的一段时间 - 我的猜测是它将在VS11之后的下一个VS发布,即用C#6.0。

  3. Mono C#编译器(参见Mark H的回答)可以做你想要的,但我不知道是否支持代码卸载或者还会泄漏一些内存。

  4. 自己动手。您知道需要支持哪个C#子集,并且可以使用单独的组件来满足上述各种“需求”。例如,NRefactory 5可以解析C#代码并分析语义。表达式树极大地简化了IL代码生成。您可以将NRefactory ResolveResults的转换器写入表达式树,这可能会在不到300行代码中解决您的问题。但是,NRefactory在其解析器中重用了Mono C#编译器的大部分内容 - 如果你采用了那么大的依赖关系,那么你也可以使用选项3。

答案 4 :(得分:0)

不确定性能部分,但这似乎是dynamic linq ...

的良好匹配

答案 5 :(得分:0)

从SiteModel类生成xsd,然后通过web / whatever-UI让管理员输入表达式,通过xsl转换输入,将表达式修改为functor literal,然后通过CodeDom生成并执行它在飞行中。

答案 6 :(得分:0)

也许您可以使用LUA Scripts作为输入。用户输入LUA表达式,您可以使用LUA引擎解析并执行它。如果需要,您可以在解释之前用其他LUA代码包装输入,并且我不确定性能。但是100次通话不是那么多。

评估表达式始终是一个安全问题。所以也要照顾好。 你可以use LUA in c#


另一种方法是编译一些包含类中输入表达式的C#代码。但是在这里,每个请求最终会有一个程序集。我认为.net 4.0可以卸载程序集,但旧版本的.net不能。所以此解决方案可能无法很好地扩展解决方法可以是每个X请求重新启动的自己的进程。

答案 7 :(得分:0)

感谢您的回答。

  • 在像我们这样的产品中引入对Mono的依赖(其安装量超过100K且发布周期为1-1。5年)对我们来说可能不是一个好的选择。这可能也是一种矫枉过正,因为我们只需要支持简单表达式(很少或没有嵌套表达式)而不是整个语言。
  • 使用代码dom编译器后,我们注意到它导致应用程序泄漏内存。虽然我们可以将它加载到一个单独的应用程序域中来解决这个问题,但这又可能是一种过度杀伤。
  • 作为VS Samples的一部分提供的动态LINQ表达式树样本有很多错误,并且在进行ding比较时不支持类型转换(将字符串更改为int,将double更改为int,将long更改为int,等等)。索引器的解析似乎也被打破了。虽然不是现成的,但它对我们的使用案例有希望。

我们现在决定使用表达树。