eval(string) to C# code

后端 未结 8 1944
傲寒
傲寒 2021-01-19 04:23

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

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

相关标签:
8条回答
  • 2021-01-19 04:43

    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.

    0 讨论(0)
  • 2021-01-19 04:45

    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.

    0 讨论(0)
  • 2021-01-19 04:46

    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

    0 讨论(0)
  • 2021-01-19 04:50

    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)
    0 讨论(0)
  • 2021-01-19 05:01

    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; }
    }
    
    0 讨论(0)
  • 2021-01-19 05:05

    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.

    0 讨论(0)
提交回复
热议问题