2009-07-30 68 views
3

是否有可能以评估在运行时在C#以下与eval(String)到C#代码

我有一个包含3个属性(FieldOperatorValue

rule.Field; 
rule.Operator; 
rule.Value; 

一类,这是我的规则类...

现在我有一个循环

foreach(item in items) 
    { 
     // here I want to create a dynamic expression to evaluate at runtime 
     // something like 
     if (item.[rule.field] [rule.operator] [rule.value]) 
      { do work } 
    } 

我只是不知道语法,或者如果它可能在C#中,我知道在JS中它可能,但这不是一种编译语言。

更新

基本上我想办法eval(stringCode)或更好更多的支持方式。

+0

然后一个代表解决方案似乎是非常接近你需要的;除非stringCode是真正动态的(用户提供的内容,即时)。然后,只要你的特定情况保持不变,你就可以通过DLR(这将在.NET 4.0中即将推出,我不知道有足够的帮助) – 2009-07-30 17:47:08

+0

需要一些东西,那么你可以实现它。如果你需要能够执行*任意*逻辑而不是所描述的固定形式,那么你就会遇到问题。基本上,只要预先知道“代码”的所有输入的集合,并且除了返回单个值(再次在编译时已知类型(即使只是作为“对象”),也没有副作用)那么这是可能的,任何超出这个范围的东西,你都不在eval这样的东西之外 – ShuggyCoUk 2009-07-30 18:29:19

回答

1

我不完全确定你在说什么。你可以尝试澄清一下吗?

你想获取一个字符串表达式并在运行时用C#评估它吗?如果是的话,答案是否定的。 C#不支持这种类型的动态评估。

2

您必须使用CodeDOM库或创建表达式树,编译它并执行它。我认为建立表达树是最好的选择。

当然,您可以在您的操作员上输入一个switch语句,这并不坏,因为您可以使用的操作员数量有限。

这里是一种用表达式树要做到这一点(写在LINQPad):

void Main() 
{ 
    var programmers = new List<Programmer>{ 
     new Programmer { Name = "Turing", Number = Math.E}, 
     new Programmer { Name = "Babbage", Number = Math.PI}, 
     new Programmer { Name = "Lovelace", Number = Math.E}}; 


    var rule0 = new Rule<string>() { Field = "Name", Operator = BinaryExpression.Equal, Value = "Turing" }; 
    var rule1 = new Rule<double>() { Field = "Number", Operator = BinaryExpression.GreaterThan, Value = 2.719 }; 

    var matched0 = RunRule<Programmer, string>(programmers, rule0); 
    matched0.Dump(); 

    var matched1 = RunRule<Programmer, double>(programmers, rule1); 
    matched1.Dump(); 

    var matchedBoth = matched0.Intersect(matched1); 
    matchedBoth.Dump(); 

    var matchedEither = matched0.Union(matched1); 
    matchedEither.Dump(); 
} 

public IEnumerable<T> RunRule<T, V>(IEnumerable<T> foos, Rule<V> rule) { 

     var fieldParam = Expression.Parameter(typeof(T), "f"); 
     var fieldProp = Expression.Property (fieldParam, rule.Field); 
     var valueParam = Expression.Parameter(typeof(V), "v"); 

     BinaryExpression binaryExpr = rule.Operator(fieldProp, valueParam); 

     var lambda = Expression.Lambda<Func<T, V, bool>>(binaryExpr, fieldParam, valueParam); 
     var func = lambda.Compile(); 

     foreach(var foo in foos) { 
      var result = func(foo, rule.Value); 
      if(result) 
       yield return foo; 
     } 

} 

public class Rule<T> { 
    public string Field { get; set; } 
    public Func<Expression, Expression, BinaryExpression> Operator { get; set; } 
    public T Value { get; set; } 
} 

public class Programmer { 
    public string Name { get; set; } 
    public double Number { get; set; } 
} 
8

没有,C#不直接支持这样的事。

最接近的选项有​​:

  • 创建一个完整有效的C#程序,动态与CSharpCodeProvider编译。
  • 建立一个expression tree,编译并执行它
  • 自己进行评估(这实际上可能是最简单的,这取决于你的运营商等)
0

您可以通过反射检索字段。然后将操作符实现为方法,并使用反射或某些类型的枚举委托映射来调用操作符。操作员应该至少有2个参数,输入值和您用来测试的值。

1

CSharpCodeProvider; switch陈述选择适当的不同的“运营商”; DLR ......他们都是你可以做到的方式;但他们似乎对我来说很奇怪。

如何使用代表?

假设你FieldValue是数字,声明是这样的:

delegate bool MyOperationDelegate(decimal left, decimal right); 
... 
class Rule { 
    decimal Field; 
    decimal Value; 
    MyOperationDelegate Operator; 
} 

现在你可以定义你的 '规则',例如,一堆的lambda表达式:

Rule rule1 = new Rule; 
rule1.Operation = (decimal l, decimal r) => { return l > r; }; 
rule1.Field = ... 

你可以制定规则阵列并以您希望的方式应用它们。

IEnumerable<Rule> items = ...; 

foreach(item in items) 
{ 
    if (item.Operator(item.Field, item.Value)) 
    { /* do work */ } 
} 

如果FieldValues不是数字或类型取决于具体的规则,你可以使用object代替decimal,并与铸造你可以把它所有的工作一点点。

这不是最终的设计;它只是给你一些想法(例如,你可能会让班级通过Check()方法或其他方法自行评估代表)。

2

你一个更好的设计是为您的规则应用测试本身(或为任意值)

通过与Func键情况下这样做,你将获得最大的灵活性,像这样:

IEnumerable<Func<T,bool> tests; // defined somehow at runtime 
foreach (var item in items) 
{ 
    foreach (var test in tests) 
    { 
     if (test(item)) 
     { 
      //do work with item 
     } 
    } 
} 

那么你的具体测试会是这样的强类型检查在编译时:

public Func<T,bool> FooEqualsX<T,V>(V x) 
{ 
    return t => EqualityComparer<V>.Default.Equals(t.Foo, x); 
} 

对于反射形式

public Func<T,bool> MakeTest<T,V>(string name, string op, V value) 
{ 
    Func<T,V> getter; 
    var f = typeof(T).GetField(name); 
    if (f != null)  
    { 
     if (!typeof(V).IsAssignableFrom(f.FieldType)) 
      throw new ArgumentException(name +" incompatible with "+ typeof(V)); 
     getter= x => (V)f.GetValue(x); 
    } 
    else 
    { 
     var p = typeof(T).GetProperty(name); 
     if (p == null)  
      throw new ArgumentException("No "+ name +" on "+ typeof(T)); 
     if (!typeof(V).IsAssignableFrom(p.PropertyType)) 
      throw new ArgumentException(name +" incompatible with "+ typeof(V)); 
     getter= x => (V)p.GetValue(x, null); 
    } 
    switch (op) 
    { 
     case "==": 
      return t => EqualityComparer<V>.Default.Equals(getter(t), value); 
     case "!=": 
      return t => !EqualityComparer<V>.Default.Equals(getter(t), value); 
     case ">": 
      return t => Comparer<V>.Default.Compare(getter(t), value) > 0; 
     // fill in the banks as you need to 
     default: 
      throw new ArgumentException("unrecognised operator '"+ op +"'"); 
    } 
} 

如果你想成为真正内省和处理任何文字不知道在编译时你可以使用CSharpCodeProvider编写一个函数假设是这样的:

public static bool Check(T t) 
{ 
    // your code inserted here 
} 

当然,这是一个巨大的所以任何可以提供代码的人都必须完全信任。这里是您的特定需求比较有限实现(没有理智可言检查)

private Func<T,bool> Make<T>(string name, string op, string value) 
{ 

    var foo = new Microsoft.CSharp.CSharpCodeProvider() 
     .CompileAssemblyFromSource(
      new CompilerParameters(), 
      new[] { "public class Foo { public static bool Eval("+ 
       typeof(T).FullName +" t) { return t."+ 
       name +" "+ op +" "+ value 
       +"; } }" }).CompiledAssembly.GetType("Foo"); 
    return t => (bool)foo.InvokeMember("Eval", 
     BindingFlags.Static | BindingFlags.Public | BindingFlags.InvokeMethod , 
     null, null, new object[] { t }); 
} 

// use like so: 
var f = Make<string>("Length", ">", "2"); 

对于这个任意类型的工作,你就必须做一些更多的思考,找到目标物的安装类型参考它在编译器参数中。

private bool Eval(object item, string name, string op, string value) 
{ 

    var foo = new Microsoft.CSharp.CSharpCodeProvider() 
     .CompileAssemblyFromSource(
      new CompilerParameters(), 
      new[] { "public class Foo { public static bool Eval("+ 
       item.GetType().FullName +" t) "+ 
       "{ return t."+ name +" "+ op +" "+ value +"; } }" 
      }).CompiledAssembly.GetType("Foo"); 
    return (bool)foo.InvokeMember("Eval", 
     BindingFlags.Static | BindingFlags.Public | BindingFlags.InvokeMethod , 
     null, null, new object[] { item }); 
} 

所有上面的代码仅仅是一种概念证明,它缺乏健全检查,并有严重的性能问题。

如果你想成为更炫,你可以使用Reflection.Emit的DynamicMethod的用实例来做到这一点(使用正确的运营商,而不是默认的比较情况),但是这将需要类型的重载运营商复杂的操作。

通过使您的检查代码具有高度通用性,您可以在将来根据需要包含更多测试。从代码中提取这些函数,基本上隔离只关心函数的代码部分,而不是从t - > true/false。

0

虽然这是事实,你可能不会找到一个优雅的方式来评估在运行完整的C#代码,而无需使用动态编译代码(这是从来没有漂亮的),你几乎可以肯定让你的规则在短期评估顺序使用DL​​R(IronPython,IronRuby等)或解析和执行自定义语法的表达式评估程序库。有一个Script.NET提供了与C#非常相似的语法。

到这里看看:Evaluating Expressions a Runtime in .NET(C#)

如果你有时间/倾向学习一点的Python,然后IronPython和DLR的将解决你所有的问题: Extending your App with IronPython

2

免责声明:我m该项目的拥有者Eval Expression.NET

该库接近于JS Eval等价物。您几乎可以评估和编译所有C#语言。

下面是一个简单的例子,使用你的问题,但库超出了这个简单的情况。

int field = 2; 
int value = 1; 
string binaryOperator = ">"; 

string formula = "x " + binaryOperator + " y"; 

// For single evaluation 
var value1 = Eval.Execute<bool>(formula, new { x = field, y = value }); 

// For many evaluation 
var compiled = Eval.Compile<Func<int, int, bool>>(formula, "x", "y"); 
var value2 = compiled(field, value);