eval(string)到C#代码

时间:2009-07-30 16:41:35

标签: c# runtime evaluation

是否可以在运行时评估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)或更好的支持方式。

8 个答案:

答案 0 :(得分:9)

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

最接近的选项是:

  • 创建一个完整的有效C#程序,并使用CSharpCodeProvider动态编译它。
  • 构建expression tree,编译并执行它
  • 自己进行评估(这可能实际上最简单,取决于您的操作员等)

答案 1 :(得分: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; }
}

答案 2 :(得分: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 - &gt;提供这些函数的代码中的true / false。

答案 3 :(得分:2)

免责声明:我是该项目的所有者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);

答案 4 :(得分:1)

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

您是否想要在C#中运行字符串表达式并在运行时对其进行评估?如果是这样,答案是否定的。 C#不支持此类动态评估。

答案 5 :(得分:1)

CSharpCodeProvider; switch语句选择适当的不同“运算符”; DLR ......他们都是你能做到这一点的方式;但它们对我来说似乎很奇怪。

如何使用代表?

假设您的FieldValue是数字,请声明如下:

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

现在你可以将你的'规则'定义为,例如,一堆lambdas:

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()方法或其他东西自己评估委托。)

答案 6 :(得分:0)

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

答案 7 :(得分:0)

虽然你可能找不到一种优雅的方法来动态评估完整的C#代码而不使用动态编译代码(这种代码永远不会很漂亮),但你几乎可以肯定地在短时间内评估你的规则使用DLR(IronPython,IronRuby等)或解析并执行自定义语法的表达式求值程序库。有一个Script.NET,它提供了与C#非常相似的语法。

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

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