Pregunta

¿Es posible evaluar lo siguiente en C # en tiempo de ejecución

?

Tengo una clase que contiene 3 propiedades ( Field , Operator , Value )

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

esta es mi clase de regla ...

Ahora tengo un bucle

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

Simplemente no sé la sintaxis, o si es posible en C #, sé que en JS es posible pero no es un lenguaje compilado.

Actualizar

Esencialmente quiero una manera de eval (stringCode) o una mejor manera más compatible.

¿Fue útil?

Solución

No estoy del todo seguro de lo que estás diciendo. ¿Puedes intentar aclararlo un poco?

¿Desea tomar una expresión de cadena y evaluarla en tiempo de ejecución en C #? Si es así, la respuesta es no. C # no admite este tipo de evaluación dinámica.

Otros consejos

No, C # no admite nada como esto directamente.

Las opciones más cercanas son:

Tendrías que usar las bibliotecas de CodeDOM o crear un árbol de expresiones, compilarlo y ejecutarlo. Creo que construir el árbol de expresiones es la mejor opción.

Por supuesto, puede poner una declaración de cambio en su operador, lo cual no está mal porque hay un número limitado de operadores que podría usar de todos modos.

Aquí hay una manera de hacer esto con árboles de expresión (escritos en 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; }
}

Un mejor diseño para usted sería que su regla aplique la prueba (o a un valor arbitrario)

Al hacer esto con las instancias de Func, obtendrá la mayor flexibilidad, así:

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

entonces su prueba específica sería algo como esto para una fuerte comprobación de tipos en tiempo de compilación:

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

Para una forma reflexiva

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

Si quisiera ser realmente introspectivo y manejar cualquier literal sin saberlo en el momento de la compilación, podría usar el CSharpCodeProvider para compilar una función asumiendo algo como:

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

Esto es, por supuesto, un agujero de seguridad masivo, por lo que quienquiera que pueda suministrar código para esto debe ser completamente confiable. Aquí hay una implementación un tanto limitada para sus necesidades específicas (sin ningún tipo de comprobación de validez)

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

Para que esto funcione con tipos arbitrarios, tendría que hacer un poco más de reflexión para encontrar el ensamblado de destino para el tipo para hacer referencia a él en los parámetros del compilador.

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

Todo el código anterior es simplemente una prueba de concepto, carece de comprobación de cordura y tiene serios problemas de rendimiento.

Si quisiera ser aún más elegante, podría usar Reflection.Emit con las instancias de DynamicMethod para hacerlo (utilizando los operadores adecuados en lugar de las instancias de comparador predeterminadas), pero esto requeriría un manejo complejo para los tipos con operadores anulados.

Al hacer que su código de verificación sea altamente genérico, puede incluir más pruebas en el futuro, según sea necesario. Esencialmente, aísle la parte de su código que solo se preocupa por una función de t - > verdadero / falso del código que proporciona estas funciones.

Descargo de responsabilidad : soy el propietario del proyecto Eval Expression.NET

Esta biblioteca está cerca de ser el equivalente de JS Eval. Casi se puede evaluar y compilar todo el lenguaje C #.

Este es un ejemplo simple que utiliza su pregunta, pero la biblioteca va más allá de este simple escenario.

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 declaraciones que seleccionan los diferentes " operadores " ;; El DLR ... son todas formas en que podrías hacer esto; pero me parecen soluciones extrañas.

¿Qué tal si solo usamos delegados?

Suponiendo que su Field y Value son números, declare algo como esto:

delegate bool MyOperationDelegate(decimal left, decimal right);
...
class Rule {
    decimal Field;
    decimal Value;
    MyOperationDelegate Operator;
}

Ahora puede definir su 'regla' como, por ejemplo, un grupo de lambdas:

Rule rule1 = new Rule;
rule1.Operation = (decimal l, decimal r) => { return l > r; };
rule1.Field = ... 

Puedes hacer matrices de reglas y aplicarlas de la forma que desees.

IEnumerable<Rule> items = ...;

foreach(item in items)
{
    if (item.Operator(item.Field, item.Value))
    { /* do work */ }
}

Si Field y Values ?? no son números, o el tipo depende de la regla específica, puede usar objeto en lugar de decimal , y con un poco de casting puedes hacer que todo funcione.

Eso no es un diseño final; es solo para darle algunas ideas (por ejemplo, es probable que la clase evalúe al delegado por su cuenta a través de un método Check () o algo así).

Puedes recuperar el campo por reflexión. Luego, implemente los operadores como métodos y utiliza la reflexión o algunos tipos de mapeo de enumeración-delegado para llamar a los operadores. Los operadores deben tener al menos 2 parámetros, el valor de entrada y el valor que está utilizando para probar con.

Si bien es cierto que probablemente no encontrará una manera elegante de evaluar el código completo de C # sobre la marcha sin el uso del código de compilación dinámica (que nunca es bonito), casi con certeza puede hacer que sus reglas se evalúen en el corto plazo utilizando el DLR (IronPython, IronRuby, etc.) o una biblioteca evaluadora de expresiones que analiza y ejecuta una sintaxis personalizada. Hay uno, Script.NET, que proporciona una sintaxis muy similar a C #.

Eche un vistazo aquí: Evaluando Expresiones de Runtime en .NET (C #)

Si tienes tiempo / ganas de aprender un poco de Python, IronPython y el DLR resolverán todos tus problemas: Extendiendo su aplicación con IronPython

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top