Frage

Ist es möglich, die folgenden in C # zur Laufzeit

zu bewerten

Ich habe eine Klasse, die drei Eigenschaften enthält (Field, Operator, Value)

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

Das ist meine Regelklasse ...

Jetzt habe ich eine Schleife

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

Ich weiß nur nicht, die Syntax, oder wenn sie möglich in C #, ich weiß, in JS seinem möglichen, aber das ist nicht eine kompilierte Sprache.

Aktualisieren

Im Grunde möchte ich einen Weg zu eval(stringCode) oder besser mehr unterstützt Weise.

War es hilfreich?

Lösung

Ich bin mir nicht ganz sicher, was Sie sagen. Können Sie es ein bisschen versuchen zu klären?

Sind Sie wollen einen String-Ausdruck zu nehmen und bewerten sie zur Laufzeit in C #? Wenn dies der Fall ist die Antwort nein. C # unterstützt keine solche Typen von dynamischer Auswertung.

Andere Tipps

Nein, C # nicht so etwas wie diese direkt unterstützen.

Die nächsten Optionen sind:

You'd have to either use the CodeDOM libraries or create an Expression tree, compile it, and execute it. I think building up the expression tree is the best option.

Of course you could put in a switch statement on your operator, which is not bad because there is a limited number of operators you could use anyways.

Here's a way to do this with expression trees (written in 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; }
}

A better design for you would be for your rule to apply the test itself (or to an arbitrary value)

By doing this with Func instances you will get the most flexibility, like so:

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

then your specific test would be something like this for strong type checking at compile time:

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

For a reflective form

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 +"'");
    }
}   

If you wanted to be really introspective and handle any literal without knowing at compile time you could use the CSharpCodeProvider to compile a function assuming something like:

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

This is of course a massive security hole so whoever can supply code for this must be fully trusted. Here is a somewhat limited implementation for your specific needs (no sanity checking at all)

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");

For this to work with arbitrary types you would have to do a bit more reflection to find the target assembly for the type to reference it in the compiler parameters.

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 });
}

All the above code is simply a proof of concept, it lacks sanity checking and has serious performance issues.

If you wanted to be even fancier you could use Reflection.Emit with DynamicMethod instances to do it (using proper operators rather than the default comparer instances) but this would require complex handling for types with overridden operators.

By making your check code highly generic you may include more tests in future as you need to. Essentially isolate the part of your code that cares only about a function from t -> true/false from the code that supplies these functions.

Disclaimer: I'm the owner of the project Eval Expression.NET

This library is close to being the JS Eval equivalent. You can almost evaluate and compile all the C# language.

Here is a simple example using your question, but the library goes way beyond this simple scenario.

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 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).

You can retrieve the field by reflection. And then implement the operators as methods and uses reflection or some types of enum-delegate mapping to call the operators. The operators should have at least 2 parameters, the input value and the value you are using to test against with.

While it is true that you probably won't find an elegant way to evaluate full C# code on the fly without the use of dynamically compiling code (which is never pretty), you can almost certainly get your rules evaluated in short order using either the DLR (IronPython, IronRuby, etc) or an expression evaluator library that parses and executes a custom syntax. There is one, Script.NET, that provides a very similar syntax to C#.

Take a look here:Evaluating Expressions a Runtime in .NET(C#)

If you have the time / inclination to learn a little Python, then IronPython and the DLR will solve all your issues: Extending your App with IronPython

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top