4

是否可以在运行时在 C# 中评估以下内容

我有一个包含 3 个属性的类(Field, Operator, Value

 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)或更好的支持方式。

4

8 回答 8

10

不,C# 不直接支持这样的东西。

最接近的选项是:

  • 创建一个完整有效的 C# 程序并使用CSharpCodeProvider.
  • 构建表达式树,编译并执行它
  • 自己执行评估(这实际上可能是最简单的,取决于您的运营商等)
于 2009-07-30T16:45:42.697 回答
4

免责声明:我是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);
于 2016-02-09T22:28:43.433 回答
2

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

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

于 2009-07-30T16:44:03.540 回答
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; }
}
于 2009-07-30T16:45:40.980 回答
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 与提供这些函数的代码隔离开来。

于 2009-07-30T17:27:02.767 回答
1

CSharpCodeProvider; switch statements that pick the proper different "operators"; the DLR... they are all ways you could do this; but they seem weird solutions to me.

How about just using delegates?

Assuming your Field and Value are numbers, declare something like this:

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

Now you can define your 'rule' as, for example, a bunch of lambdas:

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

You can make arrays of rules and apply them whichever way you wish.

IEnumerable<Rule> items = ...;

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

If Field and Values are not numbers, or the type depends on the specific rule, you can use object instead of decimal, and with a little bit of casting you can make it all work.

That's not a final design; it's just to give you some ideas (for example, you would likely have the class evaluate the delegate on its own via a Check() method or something).

于 2009-07-30T17:20:26.837 回答
0

确实,如果不使用动态编译代码(这永远不会很漂亮),您可能无法找到一种优雅的方式来动态评估完整的 C# 代码,但您几乎可以肯定地使用以下任一方法在短时间内评估您的规则DLR(IronPython、IronRuby 等)或解析和执行自定义语法的表达式评估器库。Script.NET 提供了与 C# 非常相似的语法。

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

如果您有时间/有兴趣学习一点 Python,那么 IronPython 和 DLR 将解决您的所有问题: 使用 IronPython 扩展您的应用程序

于 2009-07-30T18:19:58.777 回答
0

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

于 2009-07-30T16:50:38.613 回答