C# LanguageArboles de expresion


Introducción

Los árboles de expresión son expresiones organizadas en una estructura de datos en forma de árbol. Cada nodo en el árbol es una representación de una expresión, una expresión que es código. Una representación en memoria de una expresión Lambda sería un árbol de expresión, que contiene los elementos reales (es decir, el código) de la consulta, pero no su resultado. Los árboles de expresión hacen que la estructura de una expresión lambda sea transparente y explícita.

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:

  1. Crear una variable para almacenar el impuesto total.
  2. Recorrer todas las líneas del pedido.
  3. Para cada línea, compruebe si el producto está sujeto a impuestos.
  4. Si es así, multiplique el total de la línea por la tasa de impuesto aplicable y agregue esa cantidad al total
  5. 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, una MethodCallExpression 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 de Delegate (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.

  1. Escriba una consulta usando una expresión lambda: products.Where(x => x.Cost > 5)
  2. 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".
  3. El proveedor de consultas analiza el árbol de expresiones y genera una consulta SQL válida SELECT * FROM products WHERE Cost > 5
  4. 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.

Arboles de expresion Ejemplos relacionados