2012-03-07 73 views
3

愿我们分析类型的表达式:是否有任何可用于评估对象表达式的现成组件?

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

我明白,这是比较容易构建表达式树和评价,但我想获得围绕在表达式树上进行编译的开销。

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

是否有任何组件可以从字符串解析C#表达式并对它们进行评估? (对于C#表达服务,我觉得有这样的供VB所使用的WF4)

编辑: 我们有上,我们需要评估它们进入了IT管理员的表达的具体型号。

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

我们希望为他们进入像的表达式:

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

然后,我们想生成函数求其可以通过使SiteModel对象进行评估。

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

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

+2

你看过MSDN上的动态LINQ示例吗? [here](http://msdn.microsoft.com/en-us/library/bb397982(v = vs.90).aspx) - 但也可能是某个VS2010版本 – 2012-03-07 14:01:44

+0

您可以举一个具体的例子吗?你正在评估一个像'3 * sin(x)'这样的字符串吗? – 2012-03-07 14:22:03

+0

更新了示例。 – 2012-03-07 15:07:34

回答

0

谢谢您的回答。

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

我们已决定从现在开始使用表达式树。

+0

你好,只是另一个建议,你可能想看看[Irony](http://irony.codeplex.com/)。它是一个完整的LALR解析器引擎,完全用C#编写 - 它功能非常强大且易于使用(语法用C#语法表示),并附带一个表达式求值器示例,可能很容易修改以适应您的需求) - 祝你好运! – 2012-03-16 19:59:52

1

Mono.CSharp可以从字符串评估表达式,并且使用非常简单。 mono编译器和运行库提供了必需的参考。 (在工具目录iirc中)。

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

接下来设置评估者以了解您的代码(如有必要)。

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

然后计算表达式是调用评估一样简单。

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

恐怕Mono对我们来说不是一种选择(我们对我们添加的依赖有严格的评论)。 – 2012-03-07 14:39:38

+0

Mono C#编译器可以在.NET Framework上运行,它不需要Mono运行时。 – Daniel 2012-03-15 17:10:48

+0

我知道这个答案是4岁,希望你仍然可以帮助:是否有可能让Mono.CSharp评估一个类的实例的表达式,而不仅仅是它的静态成员? – Brandon 2016-09-29 11:53:51

1

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

我们一直在成功使用它一段时间。它甚至允许你在表达式中引用变量。

1

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

编辑:由@Asti的精确定位,这种方法创建,不幸的是,由于.NET框架设计的局限性,无法卸载,因此仔细考虑应该使用它之前进行动态组件。这意味着如果更新脚本,则包含以前版本脚本的旧程序集无法从内存中卸载,并且将一直存在,直到重新启动托管该脚本的应用程序或服务为止。

在脚本中变化的频率降低,并在编译脚本缓存和重用,而不是重新编译在每次使用时,此内存泄漏可以安全地IMO容忍(这一直是所有情况下的场景我们使用这种技术)。幸运的是,在我的经验中,生成的程序集的典型脚本内存占用往往是相当小的。

如果这是不能接受的,则该脚本可以在一个单独的应用程序域,可以从存储器中删除编译,虽然这将需要呼叫域之间编组(例如命名管道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 =) 
     } 
     } 

如您所见,您需要添加样板代码,如包装类定义,注入程序集依赖dencies,等等),但是这是一个非常强大的技术,增加了具有完全C#语法的脚本功能,并执行几乎一样快,静态代码。 (调用会慢一点)。

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

希望这会有所帮助!

+0

对不起,这是一个坏主意 - 这会在当前域的内存中创建一个程序集,而且afaik没有已知的方式来卸载它。准备好泄漏。 – Asti 2012-03-15 15:28:18

+0

@Asti很好的电话,这是一个潜在的问题,谢谢指出。恕我直言,无论是好的还是坏的想法,都取决于使用模式。在我们使用这种技术的场景中,脚本被缓存并且不经常改变。如果这是一个问题,可以将它们加载到单独的应用程序域中,以便在更新脚本时将其关闭(尽管这需要在域之间进行封送)再次感谢警告。 – 2012-03-15 18:10:09

+0

伟大的编辑,@sgorozco! – Asti 2012-03-16 06:19:40

0

不知道的性能部分,但是这似乎是一个很好的匹配dynamic linq ...

+0

动态linq不适用于很多用例。代码似乎没有写的很好,而且我们的很多测试都失败了。 – 2012-03-11 18:06:27

0

生成XSD出SiteModel类的,然后通过网络/不管-UI让管理员输入的表达,将输入通过在那里你修改表达字面一个仿函数,然后生成并通过CodeDom对飞执行它XSL。

0

也许你可以使用LUA Scripts作为输入。用户输入一个LUA表达,你可以分析和处理LUA引擎执行它。如果需要,你可以用一些其他的LUA代码包输入您解释之前,我不知道的性能。但是100个电话并不是那么多。

评估表达式始终是一个安全问题。所以也要注意这一点。 您可以use LUA in c#


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

1

“组件”你说的是:

  • 需要理解C#语法(解析您输入的字符串)
  • 需要理解C#语法(其中执行隐含的内部 - >双重转换,等)
  • 需要生成IL代码

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

  1. 当前的Microsoft C#编译器是差的选择,因为它在一个单独的处理(因此增加编译时间,因为所有的元数据需要被加载到处理)运行,并且可以只编译充满组件(和.NET组件无法卸载整个AppDomain而无法卸载,从而泄漏内存)。但是,如果你能忍受这些限制,这是一个简单的解决方案 - 请参阅sgorozco的答案。

  2. 未来微软C#编译器(罗斯林项目)就可以做你想做的,但仍然在未来的一段时间 - 我的猜测是,它会与VS11之后的下一个VS,即与被释放C#6.0。 Mono C#编译器(请参阅Mark H的答案)可以做你想做的事情,但我不知道它是否支持代码卸载或者也会泄漏一点内存。

  3. 自己动手。您知道需要支持哪个C#子集,并且有不同的组件可用于上述各种“需求”。例如,NRefactory 5可以解析C#代码并分析语义。表达式树极大地简化了IL代码生成。你可以编写一个从NRefactory ResolveResults到表达式树的转换器,这可能会用不到300行代码解决你的问题。然而,NRefactory重用在其解析器Mono的C#编译器的大部分 - 如果你正在做的是大的依赖性,你还不如用选项去3.

+0

+1我惊讶于这样的质量答案没有更多upvotes。 – 2012-03-16 02:32:41