eval(string) to C# code

后端 未结 8 1949
傲寒
傲寒 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: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 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 FooEqualsX(V x)
    {
        return t => EqualityComparer.Default.Equals(t.Foo, x);
    }
    

    For a reflective form

    public Func MakeTest(string name, string op, V value)
    {
        Func 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.Default.Equals(getter(t), value);
            case "!=":
                return t => !EqualityComparer.Default.Equals(getter(t), value);
            case ">":
                return t => Comparer.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 Make(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("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.

提交回复
热议问题