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

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

FuncFuncFunc等。

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

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

是否有任何组件可以从字符串中解析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 

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

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

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

Mono.CSharp可以从字符串中计算表达式,并且使用起来非常简单。 所需的引用随单声道编译器和运行时一起提供。 (在工具目录中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"); 

也许ILCalc (在codeplex上)做你想要的。 它来自.NET和Silverlight版本,并且是开源的。

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

你正在谈论的“组件”:

  • 需要理解C#语法(用于解析输入字符串)
  • 需要理解C#语义(在哪里执行隐式int-> double转换等)
  • 需要生成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。

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

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

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

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

结束编辑

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

 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#语法添加脚本function,并且执行速度几乎与静态代码一样快。 (调用会慢一点)。

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

希望这可以帮助!

不确定性能部分,但这似乎是动态linq的一个很好的匹配…

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

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

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


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

谢谢你的回答。

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

我们已决定使用表达式树。