English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Árbol de expresiones LINQ

Ya has conocido la expresión en la sección anterior. Ahora, permítenos entender el árbol de expresiones aquí.

Como su nombre indica, el árbol de expresiones no es más que una expresión organizada en una estructura de datos en forma de árbol. Cada nodo en el árbol de expresiones es una expresión. Por ejemplo, el árbol de expresiones se puede usar para representar la fórmula matemática x < y, donde x, < y se representarán como expresiones y se organizarán en una estructura en forma de árbol.

El árbol de expresiones es la representación en memoria de la expresión lambda. Guarda los elementos reales de la consulta, no el resultado de la consulta.

El árbol de expresiones hace que la estructura de la expresión lambda sea transparente y explícita. Puede interactuar con los datos en el árbol de expresiones, al igual que con cualquier otra estructura de datos.

Por ejemplo, vea la expresión isTeenAgerExpr a continuación:

Expression<Func<Student, bool>> isTeenAgerExpr = s => s.age > 12 && s.age < 20;

El compilador convertirá la expresión superior en el siguiente árbol de expresiones:

Ejemplo: árbol de expresiones en C#

Expression.Lambda<Func<Student, bool>>(
                Expression.AndAlso(
                    Expression.GreaterThan(Expression.Property(pe, "Age"), Expression.Constant(12, typeof(int))),
                    Expression.LessThan(Expression.Property(pe, "Age"), Expression.Constant(20, typeof(int)))),
                        new[] { pe });

También puedes construir manualmente el árbol de expresiones. Veamos cómo construir el árbol de expresiones para la siguiente expresión lambda simple:

Ejemplo: Delegado Func en C#:

Func<Student, bool> isAdult = s => s.age >= 18;

Este delegado de tipo Func será tratado como el siguiente método:

 C#:

public bool function(Student s)
{
  return s.Age > 18;
}

Para crear un árbol de expresiones, primero, crea una expresión de parámetro donde Student es el tipo del parámetro, 's' es el nombre del parámetro, como se muestra a continuación:

Pasos1En C#, crear expresiones de parámetro

ParameterExpression pe = Expression.Parameter(typeof(Student), "s");

Ahora, utiliza Expression.Property() para crear la expresión s.Age, donde s es el parámetro y Age es el nombre de la propiedad de Student. (Expressiones una clase abstracta que contiene métodos estáticos de ayuda para crear manualmente árboles de expresiones).

Pasos2En C#, crear expresiones de propiedad

MemberExpression me = Expression.Property(pe, "Age");

Ahora, para18Crear una expresión constante:

Pasos3En C#, crear expresiones constantes

ConstantExpression constant = Expression.Constant(18, typeof(int));

Hasta ahora, hemos creado para s.Age (expresión de miembro) y18(expresión constante) se construyó un árbol de expresiones. Ahora, necesitamos verificar si la expresión de miembro es mayor que la expresión constante. Para esto, utiliza el método Expression.GreaterThanOrEqual() y pasa la expresión de miembro y la expresión constante como parámetros::

Pasos4En C#, crear expresiones binarias

BinaryExpression body = Expression.GreaterThanOrEqual(me, constant);

Por lo tanto, creamos la expresión de cuerpo de la expresión lambda s.Age> = 18 se construyó un árbol de expresiones. Ahora necesitamos conectar la expresión de parámetro y la expresión de cuerpo. Utiliza Expression.Lambda(cuerpo, array de parámetros) conectan la expresión lambda s => s.age> = 18la parte del cuerpo (cuerpo) y la parte de parámetro (parámetro):

Pasos5En C#, crear expresiones Lambda

var isAdultExprTree = Expression.Lambda<Func<Student, bool>>(body, new[] { pe });

De esta manera, puede construir un árbol de expresiones para un delegado Func simple con expresiones lambda.

Ejemplo: árbol de expresiones en C#

ParameterExpression pe = Expression.Parameter(typeof(Student), "s");
MemberExpression me = Expression.Property(pe, "Age");
ConstantExpression constant = Expression.Constant(18, typeof(int));
BinaryExpression body = Expression.GreaterThanOrEqual(me, constant);
var ExpressionTree = Expression.Lambda<Func<Student, bool>>(body, new[] { pe });
Console.WriteLine("Árbol de expresiones: {0}", ExpressionTree);
        
Console.WriteLine("Cuerpo del árbol de expresiones: {0}", ExpressionTree.Body);
        
Console.WriteLine("Cuerpo del árbol de expresiones: {0}", ExpressionTree.Body) 
                                ExpressionTree.Parameters.Count);
        
Console.WriteLine("Parámetros del árbol de expresiones: {0}", ExpressionTree.Parameters[0]);
Dim pe As ParameterExpression = Expression.Parameter(GetType(Student), "s")
Dim mexp As MemberExpression = Expression.Property(pe, "Age")
Dim constant As ConstantExpression = Expression.Constant(18, GetType(Integer))
Dim body As BinaryExpression = Expression.GreaterThanOrEqual(mexp, constant)
Dim ExpressionTree As Expression(Of Func(Of Student, Boolean)) = 
    Dim ExpressionTree As Expression(Of Func(Of Student, Boolean)) =
Expression.Lambda(Of Func(Of Student, Boolean))(body, New ParameterExpression() { pe })
Console.WriteLine("Árbol de expresiones: {0}", ExpressionTree)
        
Console.WriteLine("Cuerpo del árbol de expresiones: {0}", ExpressionTree.Body) 
                                Console.WriteLine("Número de parámetros del árbol de expresiones: {0}",
        
Console.WriteLine("Parámetros del árbol de expresiones: {0}", ExpressionTree.Parameters(0))
Salida:}}
Árbol de expresiones: s => (s.Age >= 18)
Cuerpo del árbol de expresiones: (s.Age >= 18)
Número de parámetros del árbol de expresiones: 1
Parámetros del árbol de expresiones: s

La siguiente imagen ilustra todo el proceso de creación del árbol de expresiones:

Construir un árbol de expresiones

¿Por qué elegir el árbol de expresiones?

En la sección anterior, ya hemos visto que se asigna a la expresión lambdaFunc<T>se compila a código ejecutable y se asigna a la expresión lambdaExpression<TDelegate>los tipos se compilan a árboles de expresiones.

El código ejecutable se ejecuta en el mismo dominio de aplicación para manejar la colección en memoria. La clase estática enumerable contiene métodos para implementarIEnumerable <T>métodos de extensión de colecciones en memoria de interfaces, como List <T>, Dictionary <T> y otros. Los métodos de extensión en la clase Enumerable aceptanFuncparámetro de predicado de tipo delegación. Por ejemplo:WhereEl método de extensión aceptaPredicado Func <TSource, bool>. Luego, se la compila a IL (lenguaje intermedio) para manejar la colección en memoria del mismo AppDomain.

La siguiente imagen muestra el método de extensión Where de la clase Enumerable que incluye la delegación Func como parámetro:

La delegación de Where

FuncLa delegación es código ejecutable original, por lo tanto, si se depura el código, se descubriráFuncLa delegación se representará como código opaco. No puede ver sus parámetros, tipo de retorno y cuerpo:

Delegado Func en modo depuración

FuncSe utiliza un delegado para conjuntos en memoria porque se procesará en el mismo AppDomain, pero herramientas como LINQ-a-¿Qué pasa con los proveedores de consultas remotas LINQ para SQL, EntityFramework u otros productos de terceros que ofrecen LINQ? ¿Cómo解析已编译为原始 código ejecutable lambda表达式,以了解参数,lambda表达式的返回类型以及构建运行时查询以进一步处理?答案是Árbol de expresiones.

Expression <TDelegate> se compila en una estructura de datos llamada árbol de expresiones.

Si estás depurando código, la expresión representará lo siguiente:

Árbol de expresiones en modo depuración

Ahora puedes ver la diferencia entre delegados comunes y expresiones. Los árboles de expresiones son transparentes. Puedes extraer información de los parámetros, el tipo de retorno y la expresión principal de la expresión, como se muestra a continuación:

Expression<Func<Student, bool>> isTeenAgerExpr = s => s.Age > 12 && s.Age < 20;
Console.WriteLine("Expression: {0}", isTeenAgerExpr);
        
Console.WriteLine("Tipo de expresión: {0}", isTeenAgerExpr.NodeType);
var parameters = isTeenAgerExpr.Parameters;
foreach (var param in parameters)
{
    Console.WriteLine("Nombre del parámetro: {0}", param.Name);
    Console.WriteLine("Tipo de parámetro: {0}", param.Type.Name);
}
var bodyExpr = isTeenAgerExpr.Body as BinaryExpression;
Console.WriteLine("Lado izquierdo de la expresión: {0}", bodyExpr.Left);
Console.WriteLine("Tipo de expresión binaria: {0}", bodyExpr.NodeType);
Console.WriteLine("Lado derecho de la expresión: {0}", bodyExpr.Right);
Console.WriteLine("Retorno de tipo: {0}", isTeenAgerExpr.ReturnType);
Salida:}}
Expresión: s => ((s.Age > 12) AndAlso (s.Age < 20))
Tipo de expresión: Lambda
Nombre del parámetro: s
Tipo de parámetro: Student
Lado izquierdo del cuerpo de expresión: (s.Age > 12)
Tipo de expresión binaria: AndAlso
Lado derecho del cuerpo de expresión: (s.Age < 20)
Tipo de retorno: System.Boolean

No se ejecuta en el mismo dominio de aplicación para LINQ-a-Consulta LINQ de SQL o Entity Framework. Por ejemplo, la siguiente consulta LINQ de Entity Framework nunca se ejecutará realmente en el interior del programa:

Ejemplo: consulta LINQ en C#
var query = from s in dbContext.Students
            where s.Age >= 18
            select s;

Primero se convierte en una sentencia SQL y luego se ejecuta en el servidor de base de datos.

El código encontrado en la expresión de consulta debe convertirse en una consulta SQL que se puede enviar como cadena a otro proceso. Para LINQ-a-SQL o Entity Framework, este proceso es exactamente la base de datos SQL Server. Convertir una estructura de datos (como un árbol de expresiones) a SQL es mucho más fácil que convertir código IL o ejecutable original en SQL, porque, como puede ver, extraer información de la expresión es fácil.

El objetivo de crear un árbol de expresiones es convertir código como las expresiones de consulta en cadenas de caracteres que se pueden transmitir a otro proceso y ejecutar aquí.

Las clases estáticas consultables incluyen métodos de extensión que aceptan parámetros de predicado de tipo Expression. La expresión predica se convierte en un árbol de expresiones y luego se transmite como estructura de datos a un proveedor LINQ remoto, para que el proveedor pueda construir la consulta adecuada a partir del árbol de expresiones y ejecutar la consulta.