是否有可能评估以下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) 或者一个更好的更多支持的方式。

有帮助吗?

解决方案

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

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

其他提示

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

最近的选择是:

  • 创建一个充分有效C#程序和动态汇编它 CSharpCodeProvider.
  • 建立一个 表达树, ,编制和执行它
  • 执行评估自己的(这实际上可能是最简单,根据您的经营者等)

您必须使用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; }
}

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

通过使用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。

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

<代码> CSharpCodeProvider ;选择正确的不同“运营商”的 switch 语句; DLR ......他们都是你能做到这一点的方式;但它们对我来说似乎很奇怪。

如何使用代表?

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

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 */ }
}

如果 Field Values 不是数字,或者类型取决于特定规则,则可以使用 object 而不是十进制,只需一点点投射就可以使它全部工作。

这不是最终设计;它只是给你一些想法(例如,你可能会让这个类通过 Check()方法或其他东西自己评估委托。)

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

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

看看这里:评估表达.NET中的运行时(C#)

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

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top