Aplicar un lambda creado dinámicamente a una instancia de objeto

c# expression expression-trees lambda

Pregunta

Tengo un código que crea dinámicamente un lambda a partir de cadenas. Por ejemplo, tengo una clase de filtro como la siguiente:

public class Criteria {
    public string Property { get; set; }
    public string Operator { get; set; }
    public string Value { get; set; }
}

Y puedo crear una lambda como x => x.Name == "Foo" partir de una instancia de Criteria como esta

Criteria c = new Criteria() {
    Property = "Name",
    Operator = "equal",
    Value = "Foo"
}

Suponiendo tener una clase como

public class Receipt {
    public string Name { get; set; }
    public int Amount { get; set; }
    [other props omitted]
    public ICollection<ReceiptDetail> Details { get; set; }
}

Me gustaría:

  1. Aplique la lambda a cualquier objeto (sé que la lambda debe crearse con una ParameterExpression de la clase Receipt)
  2. Recuperar el resultado booleano de la lambda (por ejemplo, ¿el nombre es igual a Foo?)
  3. Aplique la misma lógica en el método Count () de las colecciones (por ejemplo, compilación de un lambda que verifique contra el recibo.

es posible?

EDITAR : Según los comentarios, estoy elaborando mis necesidades un poco más. Este código me dará la oportunidad de responder a un requisito que tengo y dice: Si hay una regla especificada para mi objeto, entonces la aplicación debería comportarse de manera un poco diferente. Si bien este es un requisito común, me gustaría crear un código que me permita extenderlo a medida que se agreguen más reglas. En realidad solo tengo 5 tipos de reglas:

  • Verifique si la entrada viene en un día específico de la semana
  • Verifique si la entrada viene en un rango de tiempo específico
  • Verifique si el campo "X" de la entrada es menor / igual / mayor que un valor
  • Verifique si el campo "Y" de la entrada contiene un valor
  • Verifique si el campo "Z" de la entrada, que es una colección, tiene una cuenta que es menor / igual / más grande que un valor

Para los primeros 4 puntos he podido crear dinámicamente una expresión lambda, con código como en la respuesta de P.Brian.Mackey , que podría aplicar, usando el patrón de Especificación, al objeto en sí.

El último punto debe implementarse casi de la misma forma, pero la única diferencia es que la parte izquierda de la expresión fue una llamada al método y no una propiedad (específicamente el método ICollection<T>.Count() )

Respuesta aceptada

Aquí hay algo para empezar. Hay mucho espacio para mejorar. Especialmente la fábrica fea. Esta demostración está diseñada para mostrar cómo usar Expressions para resolver los problemas, no como una demostración de mejores prácticas o patrones de fábrica. Si algo no está claro, por favor no dude en pedir una aclaración.

Uso

    [Test]
    public void ApplySameLogicToCollectionsCount()
    {
        var receipt = new Receipt();
        var details = new ReceiptDetail();
        var details2 = new ReceiptDetail();
        receipt.Details.Add(details);
        receipt.Details.Add(details2);
        var result = LambdaGeneratorFactory<ICollection<ReceiptDetail>>.Run(detailsCount);
        Assert.IsTrue(result(receipt.Details));
    }

Fábrica

 public static class LambdaGeneratorFactory<T>
    {
        //This is an ugly implementation of a Factory pattern.
        //You should improve this possibly with interfaces, maybe abstract factory.  I'd start with an ICriteria.
        public static Predicate<T> Run(Criteria criteria)
        {
            if (typeof(T) == typeof (Receipt))
            {
                return CreateLambda(criteria);
            }
            else if (typeof (T) == typeof (ICollection<ReceiptDetail>))
            {
                return CreateLambdaWithCount(criteria);
            }

            return null;
        }
        private static Predicate<T> CreateLambda(Criteria criteria)
        {
            ParameterExpression pe = Expression.Parameter(typeof(T), "i");

            Expression left = Expression.Property(pe, typeof(T).GetProperty(criteria.Property));
            Expression right = Expression.Constant(criteria.Value);

            Expression predicateBody = Expression.Equal(left, right);

            var predicate = Expression.Lambda<Predicate<T>>(predicateBody, new ParameterExpression[] { pe }).Compile();

            return predicate;
        }

        private static Predicate<T> CreateLambdaWithCount(Criteria criteria)
        {
            ParameterExpression pe = Expression.Parameter(typeof(T), "i");

            Expression count = Expression.Property(pe, typeof(T).GetProperty("Count"));
            Expression left = Expression.Call(count, typeof(Object).GetMethod("ToString"));
            Expression right = Expression.Constant(criteria.Value);

            Expression predicateBody = Expression.Equal(left, right);

            var predicate = Expression.Lambda<Predicate<T>>(predicateBody, new ParameterExpression[] { pe }).Compile();

            return predicate;
        }
    }

Criterios

    private Criteria detailsCount = new Criteria()
    {
        Property = "Details",
        Operator = "equal",
        Value = "2"
    };

Cambia a ICriteria y las cosas estarán más limpias. Una fábrica mejor y sin necesidad de ToString . Programa a una interfaz.

Todo lo que se dice, este código se siente un poco raro. ¿Cuál es el punto de generar funciones a partir de cadenas? Tengo la sensación de que esto se dirige hacia la generación de C # desde una gramática. No estoy convencido de que vaya a escalar bien. Para implementaciones no triviales, considere lex / yacc primero. Puede encontrar más detalles para hacer esto en el Programador Pragmático "Implementación de un mini lenguaje".


Respuesta popular

La suya es una pregunta fascinante, y me gustaría entender los requisitos. Creé una demostración, y me pregunto, ¿en qué se diferencia la demostración de lo que estás tratando de lograr? Hay una versión funcional aquí https://dotnetfiddle.net/AEBZ1w también.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

public class Program
{
    public static void Main()
    {
        Criteria c = new Criteria() { 
            Property = "Name", 
            Operator = "==", 
            Value = "Foo" };

        var queryable = (new List<Receipt>() { 
            new Receipt { Name = "Foo", Amount = 1 },
            new Receipt { Name = "Foo", Amount = 2 }, 
            new Receipt { Name = "Bar" }  
        }).AsQueryable();

        var parameter = Expression.Parameter(typeof(Receipt), "x");
        var property = Expression.Property(parameter, typeof(Receipt).GetProperty(c.Property));
        var constant = Expression.Constant(c.Value);
        var operation = Expression.Equal(property, constant);
        var expression = Expression.Call(
            typeof(Queryable),
            "Where",
            new Type[] { queryable.ElementType },
            queryable.Expression, 
            Expression.Lambda<Func<Receipt, bool>>(operation, new ParameterExpression[] { parameter })
        );

        Console.WriteLine("Linq Expression: {0} \n", expression.ToString());
        Console.WriteLine("Results: \n");

        var results = queryable.Provider.CreateQuery<Receipt>(expression);
        foreach(var r in results)
        {
            Console.WriteLine("{0}:{1}", r.Name, r.Amount);
        }
    }
}

public class Criteria
{
    public string Property, Operator, Value;
}

public class ReceiptDetail
{
    public string ItemName;
}

public class Receipt
{
    public string Name { get; set; }
    public int Amount;
    public ICollection<ReceiptDetail> Details;
}

Referencias



Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow