Pregunta ¿Por qué usarías Expression > en lugar de Func ?


Entiendo lambdas y el Func y Action delegados. Pero las expresiones me detienen. ¿En qué circunstancias usarías un Expression<Func<T>> en lugar de un simple viejo Func<T>?


771
2018-04-27 13:50


origen


Respuestas:


Cuando desee tratar las expresiones lambda como árboles de expresión y mirar dentro de ellos en lugar de ejecutarlos. Por ejemplo, LINQ to SQL obtiene la expresión y la convierte a la declaración SQL equivalente y la envía al servidor (en lugar de ejecutar la lambda).

Conceptualmente, Expression<Func<T>> es completamente diferente de Func<T>. Func<T> denota una delegate que es más o menos un puntero a un método y Expression<Func<T>> denota una estructura de datos del árbol para una expresión lambda. Esta estructura de árbol describe lo que hace una expresión lambda en lugar de hacer lo real. Básicamente contiene datos sobre la composición de expresiones, variables, llamadas a métodos, ... (por ejemplo, contiene información como esta lambda es alguna constante + algún parámetro). Puede usar esta descripción para convertirlo a un método real (con Expression.Compile) o hacer otras cosas (como el ejemplo LINQ to SQL) con él. El acto de tratar a lambdas como métodos anónimos y árboles de expresión es puramente una cuestión de tiempo de compilación.

Func<int> myFunc = () => 10; // similar to: int myAnonMethod() { return 10; }

se compilará efectivamente con un método IL que no obtiene nada y devuelve 10.

Expression<Func<int>> myExpression = () => 10;

se convertirá en una estructura de datos que describe una expresión que no obtiene parámetros y devuelve el valor 10:

Expression vs Func  imagen más grande

Si bien ambos se ven iguales en el momento de la compilación, lo que el compilador genera es totalmente diferente.


956
2018-04-27 13:52



Estoy agregando una respuesta para nodos porque estas respuestas parecían ir por encima de mi cabeza, hasta que me di cuenta de lo simple que es. A veces es su expectativa de que es complicado lo que le impide "abarcarlo".

No necesitaba entender la diferencia hasta que entré en un 'error' realmente molesto tratando de usar LINQ-to-SQL genéricamente:

public IEnumerable<T> Get(Func<T, bool> conditionLambda){
  using(var db = new DbContext()){
    return db.Set<T>.Where(conditionLambda);
  }
}

Esto funcionó de maravilla hasta que comencé a recibir OutofMemoryExceptions en conjuntos de datos más grandes. Establecer puntos de interrupción dentro de la lambda me hizo darme cuenta de que estaba iterando a través de cada fila de mi tabla, uno por uno, buscando coincidencias con mi condición lambda. Esto me dejó perplejo por un tiempo, porque ¿por qué diablos está tratando mi tabla de datos como un IEnumerable gigante en lugar de hacer LINQ-to-SQL como debería? También estaba haciendo exactamente lo mismo en mi contraparte LINQ-a-MongoDb.

La solución fue simplemente convertir Func<T, bool> dentro Expression<Func<T, bool>>, así que busqué en Google por qué necesita una Expression en lugar de Func, terminando aquí.

Una expresión simplemente convierte a un delegado en un dato sobre sí mismo. Asi que a => a + 1 se convierte en algo así como "En el lado izquierdo hay una int a. En el lado derecho, agregas 1 a él ". Eso es. Puedes ir a casa ahora. Obviamente es más estructurado que eso, pero eso es esencialmente todo un árbol de expresiones realmente, nada para entender.

Entendiendo eso, queda claro por qué LINQ-to-SQL necesita una Expressiony un Func no es adecuado Func no lleva consigo una forma de entrar en sí mismo, de ver el meollo de cómo traducirlo en una consulta de SQL / MongoDb / otra. No puede ver si está haciendo una suma o una multiplicación en la resta. Todo lo que puedes hacer es ejecutarlo. Expression, por otro lado, le permite mirar dentro del delegado y ver todo lo que quiere hacer, lo que le permite traducirlo a lo que desee, como una consulta SQL. Func no funcionó porque mi DbContext estaba cegado a lo que en realidad estaba en la expresión lambda para convertirlo en SQL, por lo que hice la siguiente mejor opción y repetí ese condicional a través de cada fila en mi tabla.

Editar: exponiendo mi última oración a pedido de John Peter:

IQueryable extiende IEnumerable, por lo que los métodos de IEnumerable como Where() obtener sobrecargas que acepten Expression. Cuando pasas una Expression a eso, usted mantiene un IQueryable como resultado, pero cuando pasa un Func, estás cayendo en la base IEnumerable y obtendrás un IEnumerable como resultado. En otras palabras, sin darte cuenta de que has convertido tu conjunto de datos en una lista para ser iterada en lugar de algo para consultar. Es difícil notar una diferencia hasta que realmente mires bajo las capuchas de las firmas.


196
2018-01-05 08:04



Una consideración extremadamente importante en la elección de Expression vs Func es que los proveedores de IQueryable como LINQ to Entities pueden "digerir" lo que pasa en una Expression, pero ignorarán lo que pasa en un Func. Tengo dos publicaciones en el blog sobre el tema:

Más sobre Expresión vs Func con Entity Framework y Enamorarse de LINQ - Parte 7: Expresiones y funciones (la última sección)


91
2018-01-11 15:57



Me gustaría agregar algunas notas sobre las diferencias entre Func<T> y Expression<Func<T>>:

  • Func<T> es simplemente un Delecto Multicast normal de la vieja escuela;
  • Expression<Func<T>> es una representación de la expresión lambda en forma de árbol de expresión;
  • el árbol de expresiones se puede construir mediante la sintaxis de expresiones lambda o mediante la sintaxis API;
  • árbol de expresión se puede compilar a un delegado Func<T>;
  • la conversión inversa es teóricamente posible, pero es un tipo de descompilación, no hay funcionalidad integrada, ya que no es un proceso sencillo;
  • el árbol de expresión se puede observar / traducir / modificar a través del ExpressionVisitor;
  • los métodos de extensión para IEnumerable operan con Func<T>;
  • los métodos de extensión para IQueryable operan con Expression<Func<T>>.

Hay un artículo que describe los detalles con muestras de código:
LINQ: Func <T> vs. Expression <Func <T >>.

Espero que sea de ayuda.


60
2018-06-11 00:02



Hay una explicación más filosófica al respecto del libro de Krzysztof Cwalina (Pautas de diseño del marco: convenciones, expresiones idiomáticas y patrones para bibliotecas .NET reutilizables);

Rico Mariani

Editar para la versión sin imágenes:

La mayoría de las veces vas a querer Func o Acción si todo lo que necesita suceder es ejecutar algún código. Necesitas Expresión cuando el código necesita ser analizado, serializado u optimizado antes de ser ejecutado. Expresión es para pensar en el código, Func / Acción es para ejecutarlo.


40
2018-03-11 13:10



LINQ es el ejemplo canónico (por ejemplo, hablar con una base de datos), pero en verdad, cada vez que te importa más expresar qué hacer, en lugar de hacerlo realmente. Por ejemplo, uso este enfoque en la pila de RPC protobuf-net (para evitar la generación de código, etc.) - por lo que llama a un método con:

string result = client.Invoke(svc => svc.SomeMethod(arg1, arg2, ...));

Esto deconstruye el árbol de expresiones para resolver SomeMethod (y el valor de cada argumento), realiza la llamada RPC, actualiza cualquier ref/out args, y devuelve el resultado de la llamada remota. Esto solo es posible a través del árbol de expresiones. Cubro esto más aquí.

Otro ejemplo es cuando construyes los árboles de expresiones manualmente para compilar a un lambda, como lo hace el operadores genéricos código.


33
2018-04-27 14:13



Utilizaría una expresión cuando quiera tratar su función como datos y no como código. Puede hacer esto si quiere manipular el código (como datos). La mayoría de las veces, si no ves la necesidad de expresiones, probablemente no necesites usar una.


18
2018-04-27 13:53



La razón principal es cuando no desea ejecutar el código directamente, sino que desea inspeccionarlo. Esto puede ser por varias razones:

  • Asignación del código a un entorno diferente (es decir, código C # a SQL en Entity Framework)
  • Reemplazar partes del código en tiempo de ejecución (programación dinámica o incluso técnicas DRY simples)
  • Validación de código (muy útil al emular secuencias de comandos o al hacer análisis)
  • Serialización: las expresiones se pueden serializar de forma bastante fácil y segura, los delegados no pueden
  • Seguridad fuertemente tipada en cosas que no son intrínsecamente fuertemente tipadas, y aprovechando las comprobaciones del compilador a pesar de que está haciendo llamadas dinámicas en tiempo de ejecución (ASP.NET MVC 5 con Razor es un buen ejemplo)

15
2018-03-26 12:54