Buscar..


Observaciones

Los árboles de expresiones son estructuras de datos que se utilizan para representar expresiones de código en .NET Framework. Se pueden generar por código y atravesar programáticamente para traducir el código a otro idioma o ejecutarlo. El generador más popular de Expression Trees es el compilador de C #. El compilador de C # puede generar árboles de expresión si una expresión lambda se asigna a una variable de tipo Expresión <Func <... >>. Normalmente esto sucede en el contexto de LINQ. El consumidor más popular es el proveedor LINQ de Entity Framework. Consume los árboles de expresión dados a Entity Framework y genera un código SQL equivalente que luego se ejecuta contra la base de datos.

Árbol de expresión simple generado por el compilador de C #

Considere el siguiente código C #

Expression<Func<int, int>> expression = a => a + 1;

Debido a que el compilador de C # ve que la expresión lambda está asignada a un tipo de expresión en lugar de un tipo de delegado, genera un árbol de expresión aproximadamente equivalente a este código

ParameterExpression parameterA = Expression.Parameter(typeof(int), "a");
var expression = (Expression<Func<int, int>>)Expression.Lambda(
                                                 Expression.Add(
                                                     parameterA,
                                                     Expression.Constant(1)),
                                                 parameterA);

La raíz del árbol es la expresión lambda que contiene un cuerpo y una lista de parámetros. La lambda tiene 1 parámetro llamado "a". El cuerpo es una expresión única del tipo CLR BinaryExpression y NodeType of Add. Esta expresión representa la suma. Tiene dos subexpresiones denotadas como Izquierda y Derecha. La izquierda es la expresión de parámetro para el parámetro "a" y la derecha es una expresión constante con el valor 1.

El uso más simple de esta expresión es imprimirla:

Console.WriteLine(expression); //prints a => (a + 1)

Que imprime el código C # equivalente.

El árbol de expresiones se puede compilar en un delegado de C # y ejecutarlo CLR

Func<int, int> lambda = expression.Compile();
Console.WriteLine(lambda(2)); //prints 3

Por lo general, las expresiones se traducen a otros idiomas como SQL, pero también se pueden usar para invocar miembros privados, protegidos e internos de tipos públicos o no públicos como alternativa a Reflection.

construyendo un predicado de campo de formulario == valor

Para construir una expresión como _ => _.Field == "VALUE" en tiempo de ejecución.

Dado un predicado _ => _.Field y un valor de cadena "VALUE" , cree una expresión que compruebe si el predicado es verdadero o no.

La expresión es adecuada para:

  • IQueryable<T> , IEnumerable<T> para probar el predicado.
  • Entidad marco o Linq a SQL para crear una cláusula Where que prueba el predicado.

Este método construirá una expresión Equal apropiada que compruebe si el Field es o no igual a "VALUE" .

public static Expression<Func<T, bool>> BuildEqualPredicate<T>(
    Expression<Func<T, string>> memberAccessor,
    string term)
{
    var toString = Expression.Convert(Expression.Constant(term), typeof(string));
    Expression expression = Expression.Equal(memberAccessor.Body, toString);
    var predicate = Expression.Lambda<Func<T, bool>>(
        expression,
        memberAccessor.Parameters);
    return predicate;
}

El predicado se puede usar al incluir el predicado en un método de extensión Where .

var predicate = PredicateExtensions.BuildEqualPredicate<Entity>(
    _ => _.Field,
    "VALUE");
var results = context.Entity.Where(predicate).ToList();

Expresión para recuperar un campo estático

Teniendo un tipo de ejemplo como este:

public TestClass
{
    public static string StaticPublicField = "StaticPublicFieldValue";
}

Podemos recuperar el valor de StaticPublicField:

var fieldExpr = Expression.Field(null, typeof(TestClass), "StaticPublicField");
var labmda = Expression.Lambda<Func<string>>(fieldExpr);

Entonces puede ser compilado en un delegado para recuperar el valor del campo.

Func<string> retriever = lambda.Compile();
var fieldValue = retriever();

// el resultado de fieldValue es StaticPublicFieldValue

Clase de expresión de invocación

La clase InvocationExpression permite la invocación de otras expresiones lambda que forman parte del mismo árbol de expresiones.

Los creas con el método de Expression.Invoke estático.

Problema Queremos incluir los elementos que tienen "automóvil" en su descripción. Necesitamos verificarlo en nulo antes de buscar una cadena interna, pero no queremos que se llame en exceso, ya que el cálculo podría ser costoso.

using System;
using System.Linq;
using System.Linq.Expressions;
                    
public class Program
{
    public static void Main()
    {
        var elements = new[] {
            new Element { Description = "car" },
            new Element { Description = "cargo" },
            new Element { Description = "wheel" },
            new Element { Description = null },
            new Element { Description = "Madagascar" },
        };
    
        var elementIsInterestingExpression = CreateSearchPredicate(
            searchTerm: "car",
            whereToSearch: (Element e) => e.Description);
            
        Console.WriteLine(elementIsInterestingExpression.ToString());
            
        var elementIsInteresting = elementIsInterestingExpression.Compile();
        var interestingElements = elements.Where(elementIsInteresting);
        foreach (var e in interestingElements)
        {
            Console.WriteLine(e.Description);
        }
        
        var countExpensiveComputations = 0;
        Action incCount = () => countExpensiveComputations++;
        elements
            .Where(
                CreateSearchPredicate(
                    "car",
                    (Element e) => ExpensivelyComputed(
                        e, incCount
                    )
                ).Compile()
            )
            .Count();
                   
        Console.WriteLine("Property extractor is called {0} times.", countExpensiveComputations);
    }
    
    private class Element
    {
        public string Description { get; set; }
    }
    
    private static string ExpensivelyComputed(Element source, Action count)
    {
        count();
        return source.Description;
    }
    
    private static Expression<Func<T, bool>> CreateSearchPredicate<T>(
            string searchTerm,
            Expression<Func<T, string>> whereToSearch)
    {
        var extracted = Expression.Parameter(typeof(string), "extracted");
    
        Expression<Func<string, bool>> coalesceNullCheckWithSearch =
            Expression.Lambda<Func<string, bool>>(
                Expression.AndAlso(
                    Expression.Not(
                        Expression.Call(typeof(string), "IsNullOrEmpty", null, extracted)
                    ),
                    Expression.Call(extracted, "Contains", null, Expression.Constant(searchTerm))
                ),
                extracted);
                
        var elementParameter = Expression.Parameter(typeof(T), "element");
                
        return Expression.Lambda<Func<T, bool>>(
            Expression.Invoke(
                coalesceNullCheckWithSearch,
                Expression.Invoke(whereToSearch, elementParameter)
            ),
            elementParameter
        );
    }
}

Salida

element => Invoke(extracted => (Not(IsNullOrEmpty(extracted)) AndAlso extracted.Contains("car")), Invoke(e => e.Description, element))
car
cargo
Madagascar
Predicate is called 5 times.

Lo primero que se debe tener en cuenta es cómo el acceso real a la propiedad, envuelto en una Invocación:

Invoke(e => e.Description, element)

, y esta es la única parte que toca e.Description , y en lugar de ello, el parámetro extracted de tipo string se pasa a la siguiente:

(Not(IsNullOrEmpty(extracted)) AndAlso extracted.Contains("car"))

Otra cosa importante a tener en cuenta aquí es AndAlso . Calcula solo la parte izquierda, si la primera parte devuelve "falso". Es un error común utilizar el operador bit a bit 'And' en lugar de este, que siempre calcula ambas partes y fallaría con una NullReferenceException en este ejemplo.



Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow