C# Language
Arboles de expresion
Buscar..
Introducción
Sintaxis
- Expresión <TDelegate> name = lambdaExpression;
Parámetros
Parámetro | Detalles |
---|---|
TDelegate | El tipo de delegado que se utilizará para la expresión. |
expresión lambda | La expresión lambda (ej. num => num < 5 ) |
Observaciones
Introducción a los árboles de expresión
De donde venimos
Los árboles de expresiones tratan de consumir "código fuente" en tiempo de ejecución. Considere un método que calcula el impuesto a las ventas adeudado en una orden de venta decimal CalculateTotalTaxDue(SalesOrder order)
. El uso de ese método en un programa .NET es fácil: solo se le llama decimal taxDue = CalculateTotalTaxDue(order);
. ¿Qué sucede si desea aplicarlo a todos los resultados de una consulta remota (SQL, XML, un servidor remoto, etc.)? ¡Esas fuentes de consulta remotas no pueden llamar al método! Tradicionalmente, tendrías que invertir el flujo en todos estos casos. Realice la consulta completa, guárdela en la memoria, luego repita los resultados y calcule los impuestos para cada resultado.
Cómo evitar problemas de memoria y latencia en la inversión de flujo.
Los árboles de expresión son estructuras de datos en un formato de árbol, donde cada nodo contiene una expresión. Se utilizan para traducir las instrucciones compiladas (como los métodos utilizados para filtrar datos) en expresiones que podrían usarse fuera del entorno del programa, como dentro de una consulta de base de datos.
El problema aquí es que una consulta remota no puede acceder a nuestro método . Podríamos evitar este problema si, en cambio, enviamos las instrucciones del método a la consulta remota. En nuestro ejemplo de CalculateTotalTaxDue
, eso significa que enviamos esta información:
- Crear una variable para almacenar el impuesto total.
- Recorrer todas las líneas del pedido.
- Para cada línea, compruebe si el producto está sujeto a impuestos.
- Si es así, multiplique el total de la línea por la tasa de impuesto aplicable y agregue esa cantidad al total
- De lo contrario no hacer nada
Con esas instrucciones, la consulta remota puede realizar el trabajo a medida que crea los datos.
Hay dos desafíos para implementar esto. ¿Cómo transforma un método .NET compilado en una lista de instrucciones, y cómo formatea las instrucciones de manera que puedan ser consumidas por el sistema remoto?
Sin árboles de expresiones, solo se podría resolver el primer problema con MSIL. (MSIL es el código tipo ensamblador creado por el compilador .NET). Analizar MSIL es posible , pero no es fácil. Incluso cuando lo analiza correctamente, puede ser difícil determinar cuál fue la intención del programador original con una rutina en particular.
Árboles de expresión salvan el día
Los árboles de expresión abordan estos problemas exactos. Representan instrucciones de programa, una estructura de datos de árbol donde cada nodo representa una instrucción y tiene referencias a toda la información que necesita para ejecutar esa instrucción. Por ejemplo, unaMethodCallExpression
tiene una referencia a 1) MethodInfo
a la que llamará, 2) una lista de Expression
que pasará a ese método, 3) para los métodos de instancia, la Expression
a la que llamará el método. Puede "recorrer el árbol" y aplicar las instrucciones en su consulta remota. Creando arboles de expresion
La forma más fácil de crear un árbol de expresiones es con una expresión lambda. Estas expresiones se ven casi iguales a los métodos normales de C #. Es importante darse cuenta de que esto es magia compilador . Cuando creas una expresión lambda por primera vez, el compilador verifica a qué lo asignas. Si es un tipo deDelegate
(incluyendo Action
o Func
), el compilador convierte la expresión lambda en un delegado. Si se trata de una LambdaExpression
(o una Expression<Action<T>>
o una Expression<Func<T>>
que son de tipo LambdaExpression
), el compilador la transforma en una LambdaExpression
. Aquí es donde entra en LambdaExpression
la magia. Entre bambalinas, el compilador utiliza la API del árbol de expresiones para transformar la expresión lambda en una expresión LambdaExpression
. Las expresiones Lambda no pueden crear todo tipo de árbol de expresión. En esos casos, puede utilizar la API de expresiones manualmente para crear el árbol que necesita. En el ejemplo Entendiendo las expresiones API , creamos la expresión CalculateTotalSalesTax
utilizando la API.
NOTA: Los nombres se ponen un poco confusos aquí. Una expresión lambda (dos palabras, minúsculas) se refiere al bloque de código con un indicador =>
. Representa un método anónimo en C # y se convierte en un Delegate
o Expression
. Una LambdaExpression
(una palabra, PascalCase) se refiere al tipo de nodo dentro de la API de Expresión que representa un método que puede ejecutar.
Árboles de expresión y LINQ
Uno de los usos más comunes de los árboles de expresión es con LINQ y consultas de base de datos. LINQ empareja un árbol de expresiones con un proveedor de consultas para aplicar sus instrucciones a la consulta remota de destino. Por ejemplo, el proveedor de consultas de LINQ to Entity Framework transforma un árbol de expresiones en SQL que se ejecuta directamente en la base de datos.
Poniendo todas las piezas juntas, puedes ver el verdadero poder detrás de LINQ.
- Escriba una consulta usando una expresión lambda:
products.Where(x => x.Cost > 5)
- El compilador transforma esa expresión en un árbol de expresiones con las instrucciones "compruebe si la propiedad de costo del parámetro es mayor que cinco".
- El proveedor de consultas analiza el árbol de expresiones y genera una consulta SQL válida
SELECT * FROM products WHERE Cost > 5
- El ORM proyecta todos los resultados en POCO y obtiene una lista de objetos de vuelta
Notas
- Los árboles de expresión son inmutables. Si desea cambiar un árbol de expresiones, necesita crear uno nuevo, copie el existente en el nuevo (para recorrer un árbol de expresiones puede usar
ExpressionVisitor
) y realice los cambios deseados.
Creando árboles de expresiones usando la API
using System.Linq.Expressions;
// Manually build the expression tree for
// the lambda expression num => num < 5.
ParameterExpression numParam = Expression.Parameter(typeof(int), "num");
ConstantExpression five = Expression.Constant(5, typeof(int));
BinaryExpression numLessThanFive = Expression.LessThan(numParam, five);
Expression<Func<int, bool>> lambda1 =
Expression.Lambda<Func<int, bool>>(
numLessThanFive,
new ParameterExpression[] { numParam });
Compilando arboles de expresion
// Define an expression tree, taking an integer, returning a bool.
Expression<Func<int, bool>> expr = num => num < 5;
// Call the Compile method on the expression tree to return a delegate that can be called.
Func<int, bool> result = expr.Compile();
// Invoke the delegate and write the result to the console.
Console.WriteLine(result(4)); // Prints true
// Prints True.
// You can also combine the compile step with the call/invoke step as below:
Console.WriteLine(expr.Compile()(4));
Análisis de árboles de expresión
using System.Linq.Expressions;
// Create an expression tree.
Expression<Func<int, bool>> exprTree = num => num < 5;
// Decompose the expression tree.
ParameterExpression param = (ParameterExpression)exprTree.Parameters[0];
BinaryExpression operation = (BinaryExpression)exprTree.Body;
ParameterExpression left = (ParameterExpression)operation.Left;
ConstantExpression right = (ConstantExpression)operation.Right;
Console.WriteLine("Decomposed expression: {0} => {1} {2} {3}",
param.Name, left.Name, operation.NodeType, right.Value);
// Decomposed expression: num => num LessThan 5
Crear árboles de expresión con una expresión lambda
A continuación se muestra el árbol de expresiones más básico creado por lambda.
Expression<Func<int, bool>> lambda = num => num == 42;
Para crear árboles de expresión 'a mano', se debe usar Expression
clase de Expression
.
La expresión anterior sería equivalente a:
ParameterExpression parameter = Expression.Parameter(typeof(int), "num"); // num argument
ConstantExpression constant = Expression.Constant(42, typeof(int)); // 42 constant
BinaryExpression equality = Expression.Equals(parameter, constant); // equality of two expressions (num == 42)
Expression<Func<int, bool>> lambda = Expression.Lambda<Func<int, bool>>(equality, parameter);
Entendiendo la API de expresiones
Vamos a utilizar la API del árbol de expresiones para crear un árbol CalculateSalesTax
. En un lenguaje sencillo, aquí hay un resumen de los pasos necesarios para crear el árbol.
- Compruebe si el producto está sujeto a impuestos.
- Si es así, multiplique el total de la línea por la tasa de impuesto aplicable y devuelva esa cantidad
- De lo contrario devuelve 0
//For reference, we're using the API to build this lambda expression
orderLine => orderLine.IsTaxable ? orderLine.Total * orderLine.Order.TaxRate : 0;
//The orderLine parameter we pass in to the method. We specify it's type (OrderLine) and the name of the parameter.
ParameterExpression orderLine = Expression.Parameter(typeof(OrderLine), "orderLine");
//Check if the parameter is taxable; First we need to access the is taxable property, then check if it's true
PropertyInfo isTaxableAccessor = typeof(OrderLine).GetProperty("IsTaxable");
MemberExpression getIsTaxable = Expression.MakeMemberAccess(orderLine, isTaxableAccessor);
UnaryExpression isLineTaxable = Expression.IsTrue(getIsTaxable);
//Before creating the if, we need to create the braches
//If the line is taxable, we'll return the total times the tax rate; get the total and tax rate, then multiply
//Get the total
PropertyInfo totalAccessor = typeof(OrderLine).GetProperty("Total");
MemberExpression getTotal = Expression.MakeMemberAccess(orderLine, totalAccessor);
//Get the order
PropertyInfo orderAccessor = typeof(OrderLine).GetProperty("Order");
MemberExpression getOrder = Expression.MakeMemberAccess(orderLine, orderAccessor);
//Get the tax rate - notice that we pass the getOrder expression directly to the member access
PropertyInfo taxRateAccessor = typeof(Order).GetProperty("TaxRate");
MemberExpression getTaxRate = Expression.MakeMemberAccess(getOrder, taxRateAccessor);
//Multiply the two - notice we pass the two operand expressions directly to multiply
BinaryExpression multiplyTotalByRate = Expression.Multiply(getTotal, getTaxRate);
//If the line is not taxable, we'll return a constant value - 0.0 (decimal)
ConstantExpression zero = Expression.Constant(0M);
//Create the actual if check and branches
ConditionalExpression ifTaxableTernary = Expression.Condition(isLineTaxable, multiplyTotalByRate, zero);
//Wrap the whole thing up in a "method" - a LambdaExpression
Expression<Func<OrderLine, decimal>> method = Expression.Lambda<Func<OrderLine, decimal>>(ifTaxableTernary, orderLine);
Árbol de Expresión Básico
Los árboles de expresión representan el código en una estructura de datos similar a un árbol, donde cada nodo es una expresión
Expression Trees permite la modificación dinámica del código ejecutable, la ejecución de consultas LINQ en varias bases de datos y la creación de consultas dinámicas. Puedes compilar y ejecutar código representado por árboles de expresión.
También se usan en el tiempo de ejecución del lenguaje dinámico (DLR) para proporcionar interoperabilidad entre los lenguajes dinámicos y .NET Framework y para permitir que los escritores de compiladores emitan árboles de expresión en lugar del lenguaje intermedio de Microsoft (MSIL).
Los árboles de expresión se pueden crear a través de
- Expresión lambda anónima,
- Manualmente utilizando el espacio de nombres System.Linq.Expressions.
Árboles de expresión de Expresiones Lambda
Cuando se asigna una expresión lambda a la variable de tipo de expresión, el compilador emite código para construir un árbol de expresión que representa la expresión lambda.
Los siguientes ejemplos de código muestran cómo hacer que el compilador de C # cree un árbol de expresión que represente la expresión lambda num => num <5.
Expression<Func<int, bool>> lambda = num => num < 5;
Árboles de expresión mediante el uso de la API
Los árboles de expresión también se crearon utilizando la clase de expresión . Esta clase contiene métodos de fábrica estáticos que crean nodos de árbol de expresión de tipos específicos.
A continuación se muestran algunos tipos de nodos de árbol.
- ParameterExpression
- MethodCallExpression
El siguiente ejemplo de código muestra cómo crear un árbol de expresión que representa la expresión lambda num => num <5 mediante el uso de la API.
ParameterExpression numParam = Expression.Parameter(typeof(int), "num");
ConstantExpression five = Expression.Constant(5, typeof(int));
BinaryExpression numLessThanFive = Expression.LessThan(numParam, five);
Expression<Func<int, bool>> lambda1 = Expression.Lambda<Func<int, bool>>(numLessThanFive,new ParameterExpression[] { numParam });
Examinar la estructura de una expresión usando el visitante
Defina una nueva clase de visitante anulando algunos de los métodos de ExpressionVisitor :
class PrintingVisitor : ExpressionVisitor {
protected override Expression VisitConstant(ConstantExpression node) {
Console.WriteLine("Constant: {0}", node);
return base.VisitConstant(node);
}
protected override Expression VisitParameter(ParameterExpression node) {
Console.WriteLine("Parameter: {0}", node);
return base.VisitParameter(node);
}
protected override Expression VisitBinary(BinaryExpression node) {
Console.WriteLine("Binary with operator {0}", node.NodeType);
return base.VisitBinary(node);
}
}
Llame a Visit
para usar este visitante en una expresión existente:
Expression<Func<int,bool>> isBig = a => a > 1000000;
var visitor = new PrintingVisitor();
visitor.Visit(isBig);