¿Por qué usarías Expresión> en lugar de Func?


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

Author: Mehrdad Afshari, 2009-04-27

9 answers

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

Conceptualmente, Expression<Func<T>> es completamente diferente de Func<T>. Func<T> denota un delegate que es más o menos un puntero a un método y Expression<Func<T>>denota una estructura de datos en árbol para una expresión lambda. Este estructura de árbol describe lo que hace una expresión lambda en lugar de hacer la cosa 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 de LINQ a SQL) con él. El acto de tratar lambdas como métodos anónimos y árboles de expresión es puramente una cosa de tiempo de compilación.

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

Compilará efectivamente a 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:

Expresión vs Funcimagen más grande

Mientras que ambos tienen el mismo aspecto en tiempo de compilación, lo que genera el compilador es totalmente diferente.

 973
Author: Mehrdad Afshari,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-01-20 03:01:48

Estoy agregando una respuesta-para-noobs porque estas respuestas parecían sobre mi cabeza, hasta que me di cuenta de lo simple que es. A veces es tu expectativa de que es complicado lo que te hace incapaz de 'envolver tu cabeza alrededor de ella'.

No necesitaba entender la diferencia hasta que entré en un ' bug ' 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ó muy bien hasta que empecé a salir Dememoryexceptions en conjuntos de datos más grandes. Establecer puntos de interrupción dentro del lambda me hizo darme cuenta de que estaba iterando a través de cada fila en 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 giganteumumerable en lugar de hacer LINQ-a-SQL como se supone que debe? También estaba haciendo exactamente lo mismo en mi contraparte LINQ-a-MongoDB.

La solución era simplemente convertir Func<T, bool> en Expression<Func<T, bool>>, así que busqué en Google por qué necesita un Expression en lugar de Func, terminando aqui.

Una expresión simplemente convierte a un delegado en un dato sobre sí mismo. Así que a => a + 1 se convierte en algo así como "En el lado izquierdo hay un int a. En el lado derecho le agregas 1." Eso es todo. Ya puedes irte a casa. Obviamente está más estructurado que eso, pero eso es esencialmente todo lo que un árbol de expresiones realmente es nothing nada para envolver tu cabeza.

Entendiendo esto, queda claro por qué LINQ-to-SQL necesita un Expression, y un Func no es adecuado. Func no lleva consigo una forma de entrar en sí mismo, de ver lo esencial de cómo traducirlo en una consulta SQL/MongoDB/other. No puedes ver si está haciendo suma o multiplicación en 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 quiera, como una consulta SQL. Func no funcionó porque mi DbContext estaba ciego a lo que realmente estaba en la lambda expresión para convertirlo en SQL, por lo que hizo la siguiente mejor cosa e iteró que condicional a través de cada fila en mi tabla.

Editar: exponiendo mi última frase a petición de Juan Pedro: {[17]]}

IQueryable extiendeerableumerable, por lo que los métodos deEnumerable como Where() obtienen sobrecargas que aceptan Expression. Cuando pasas un Expression a eso, mantienes un IQueryable como resultado, pero cuando pasas un Func, estás cayendo de nuevo en la base Iumerable y obtendrás unEnumerable como resultado. En otras palabras, sin darse cuenta de que ha convertido su conjunto de datos en una lista para iterar en lugar de algo para consultar. Es difícil notar una diferencia hasta que realmente miras bajo el capó las firmas.

 220
Author: Chad Hedgcock,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-10-07 16:34:01

Una consideración extremadamente importante en la elección de Expresión vs Func es que los proveedores IQueryables como LINQ to Entities pueden 'digerir' lo que pasa en una Expresión, pero ignorarán lo que pasa en un Func. Tengo dos entradas de 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)

 92
Author: LSpencer777,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-01-11 15:57:43

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

  • Func<T> es solo un MulticastDelegate normal de la vieja escuela;
  • {[1] } es una representación de la expresión lambda en forma de árbol de expresión;
  • el árbol de expresiones se puede construir a través de la sintaxis de expresiones lambda o a través de la sintaxis API;
  • el árbol de expresiones se puede compilar a un delegado Func<T>;
  • la conversión inversa es teóricamente posible, pero es una especie de descompilando, no hay ninguna funcionalidad incorporada para eso, ya que no es un proceso sencillo;
  • el árbol de expresiones se puede observar / traducir / modificar a través de ExpressionVisitor;
  • los métodos de extensión paraEnumerable funcionan con Func<T>;
  • los métodos de extensión para IQueryable funcionan con Expression<Func<T>>.

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

Espero que sea útil.

 60
Author: Olexander,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-06-11 08:34:04

Hay una explicación más filosófica al respecto en el libro de Krzysztof Cwalina (Framework Design Guidelines: Conventions, Modoms, and Patterns for Reusable. NET Libraries);

Rico Mariani

Editar para la versión que no es imagen:

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

 46
Author: Oğuzhan Soykan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-10-11 18:00:54

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 lo que hacer, en lugar de hacerlo realmente. Por ejemplo, utilizo este enfoque en la pila RPC de protobuf-net (para evitar la generación de código, etc.), por lo que llamas 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 está construyendo los árboles de expresiones manualmente con el propósito de compilar a una lambda, como lo hace el código de los operadores genéricos .

 32
Author: Marc Gravell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-08-13 05:57:02

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

 17
Author: Andrew Hare,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-04-27 13:53:05

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

  • Asignar el código a un entorno diferente (es decir. Código C # a SQL en Entity Framework)
  • Sustitución de partes del código en tiempo de ejecución (programación dinámica o incluso técnicas SECAS simples)
  • Validación de código (muy útil al emular scripts o al hacer análisis)
  • Serialización-las expresiones pueden ser serializadas fácil y seguro, los delegados no pueden
  • Seguridad fuertemente mecanografiada en cosas que no están inherentemente fuertemente mecanografiadas, y explotando las comprobaciones del compilador aunque estés haciendo llamadas dinámicas en tiempo de ejecución (ASP.NET MVC 5 con Razor es un buen ejemplo)
 15
Author: Luaan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-03-26 12:54:33

Todavía no veo ninguna respuesta que mencione el rendimiento. Pasar Func<> s a Where() o Count() es malo. Muy mal. Si utilizas un Func<> entonces llama a las cosas IEnumerable LINQ en lugar de IQueryable, lo que significa que las tablas enteras se extraen y luego se filtran. Expression<Func<>> es significativamente más rápido, especialmente si está consultando una base de datos que vive en otro servidor.

 5
Author: mhenry1384,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-06-16 15:58:20