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

Resuelto Richard Nagle asked hace 15 años • 12 respuestas

Entiendo lambdas y los Funcdelegados Action. Pero las expresiones me desconciertan.

¿ En qué circunstancias utilizarías un Expression<Func<T>>en lugar de uno simple y antiguo Func<T>?

Richard Nagle avatar Apr 27 '09 20:04 Richard Nagle
Aceptado

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

Conceptualmente, Expression<Func<T>>es completamente diferente a Func<T>. Func<T>denota a delegateque es prácticamente un puntero a un método y Expression<Func<T>>denota una estructura de datos de árbol para una expresión lambda. Esta estructura de árbol describe lo que hace una expresión lambda en lugar de hacer lo que realmente hace. Básicamente contiene datos sobre la composición de expresiones, variables, llamadas a métodos, ... (por ejemplo, contiene información como que esta lambda es una constante + algún parámetro). Puedes usar esta descripción para convertirlo en un método real (con Expression.Compile) o hacer otras cosas (como el ejemplo de LINQ to SQL) con él. El acto de tratar 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; }

compilará efectivamente en 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 Función imagen más grande

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

Mehrdad Afshari avatar Apr 27 '2009 13:04 Mehrdad Afshari

Estoy agregando una respuesta para novatos porque estas respuestas parecían pasarse por alto, 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 "entenderlo".

No necesitaba entender la diferencia hasta que encontré un 'error' realmente molesto al intentar 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 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 una por una buscando coincidencias con mi condición lambda. Esto me dejó perplejo por un tiempo, porque ¿por qué diablos trata mi tabla de datos como un IEnumerable gigante en lugar de hacer LINQ-to-SQL como se supone que debe hacerlo? También estaba haciendo exactamente lo mismo en mi contraparte de LINQ a MongoDb.

La solución fue simplemente convertirse Func<T, bool>en Expression<Func<T, bool>>, así que busqué en Google por qué necesita un Expressionlugar de Func, y terminé aquí.

Una expresión simplemente convierte a un delegado en datos sobre sí mismo. Entonces a => a + 1se convierte en algo como "En el lado izquierdo hay un int a. En el lado derecho le agregas 1". Eso es todo. Puedes irte a casa ahora. Obviamente está más estructurado que eso, pero eso es esencialmente todo lo que es un árbol de expresión: nada que entienda.

Al comprender esto, queda claro por qué LINQ-to-SQL necesita un Expressiony Funcno es adecuado. Funcno lleva consigo una manera de entrar en sí mismo, para ver el meollo de la cuestión de cómo traducirlo a una consulta SQL/MongoDb/otra. No puedes ver si está sumando, multiplicando o restando. Todo lo que puedes hacer es ejecutarlo. Expression, por otro lado, le permite mirar dentro del delegado y ver todo lo que quiere hacer. Esto le permite traducir el delegado a lo que desee, como una consulta SQL. Funcno funcionó porque mi DbContext no conocía el contenido de la expresión lambda. Debido a esto, no pudo convertir la expresión lambda en SQL; sin embargo, hizo lo mejor que pudo y repitió ese condicional en cada fila de mi tabla.

Editar: exponiendo mi última frase a petición de John Peter:

IQueryable extiende IEnumerable, por lo que los métodos de IEnumerable, como Where()obtener sobrecargas que aceptan Expression. Cuando pasas un Expressiona eso, mantienes un IQueryable como resultado, pero cuando pasas un Func, estás recurriendo al IEnumerable base y obtendrás un IEnumerable como resultado. En otras palabras, sin darte cuenta, has convertido tu conjunto de datos en una lista para iterar en lugar de algo para consultar. Es difícil notar la diferencia hasta que realmente miras las firmas debajo del capó.

Chad Hedgcock avatar Jan 05 '2016 08:01 Chad Hedgcock