eval(string) to C# code

只愿长相守 提交于 2020-07-03 07:04:08

问题


Is it possible to evaluate the following in C# at runtime

I have a class that contains 3 properties (Field,Operator,Value)

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

this is my rule class...

Now I have a loop

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

I just don't know the syntax, or if its possible in C#, I know in JS its possible but that's not a compiled language.

Update

Essentially I want a way to eval(stringCode) or a better more supported way.


回答1:


I'm not entirely sure what you are saying. Can you try clarifying it a bit?

Are you wanting to to take a string expression and evaluate it at runtime in C#? If so the answer is no. C# does not support such types of dynamic evaluation.




回答2:


No, C# doesn't support anything like this directly.

The closest options are:

  • Create a full valid C# program and dynamically compile it with CSharpCodeProvider.
  • Build an expression tree, compile and execute it
  • Perform the evaluation yourself (this may actually be easiest, depending on your operators etc)



回答3:


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



回答4:


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



回答5:


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.




回答6:


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




回答7:


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.




回答8:


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



来源:https://stackoverflow.com/questions/1207809/evalstring-to-c-sharp-code

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!