Entiendo lambdas y la Func
y Action
delegados. Pero las expresiones me sorprenden.
¿En qué circunstancias Expression<Func<T>>
usarías un viejo en lugar de uno viejo Func<T>
?
c#
delegates
lambda
expression-trees
Richard Nagle
fuente
fuente
Respuestas:
Cuando desee tratar las 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 y la convierte a la instrucción SQL equivalente y la envía al servidor (en lugar de ejecutar el lambda).
Conceptualmente,
Expression<Func<T>>
es completamente diferente deFunc<T>
.Func<T>
denota undelegate
que es más o menos un puntero a un método yExpression<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 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 convertirla a un método real (conExpression.Compile
) o hacer otras cosas (como el ejemplo LINQ to SQL) con ella. El acto de tratar las lambdas como métodos anónimos y árboles de expresión es puramente una cuestión de tiempo de compilación.efectivamente compilará a un método IL que no obtiene nada y devuelve 10.
se convertirá en una estructura de datos que describe una expresión que no obtiene parámetros y devuelve el valor 10:
imagen más grande
Si bien ambos se ven iguales en tiempo de compilación, lo que genera el compilador es totalmente diferente .
fuente
Expression
contiene la metainformación sobre cierto delegado.Expression<Func<...>>
lugar de soloFunc<...>
.(isAnExample) => { if(isAnExample) ok(); else expandAnswer(); }
tal expresión es un árbol de expresión, se crean ramas para la instrucción If.Estoy agregando una respuesta para noobs porque estas respuestas parecían pasar por alto, hasta que me di cuenta de lo simple que es. A veces es su expectativa que es complicado lo que le hace incapaz de 'entenderlo'.
No necesitaba entender la diferencia hasta que me encontré con un 'error' realmente molesto que intentaba usar LINQ-to-SQL genéricamente:
Esto funcionó muy bien hasta que comencé a obtener OutofMemoryExceptions 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 trata a 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 homólogo de LINQ-to-MongoDb.
La solución era simplemente convertir
Func<T, bool>
enExpression<Func<T, bool>>
, por lo busqué en Google por lo que necesita unExpression
lugar deFunc
, terminando aquí.Una expresión simplemente convierte a un delegado en datos sobre sí misma. Entonces se
a => a + 1
convierte en algo así como "En el lado izquierdo hay unint a
. En el lado derecho le agregas 1". Eso es. Puedes irte a casa ahora. Obviamente, está más estructurado que eso, pero eso es esencialmente todo un árbol de expresión, nada que pueda comprender.Entendiendo eso, queda claro por qué LINQ-to-SQL necesita un
Expression
, y unFunc
no es adecuado.Func
no lleva consigo una forma de meterse en sí mismo, para ver lo esencial de cómo traducirlo a una consulta SQL / MongoDb / other. No puedes ver si se trata de sumar, multiplicar o restar. 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.Func
no funcionó porque mi DbContext era ciego al contenido de la expresión lambda. Debido a esto, no pudo convertir la expresión lambda en SQL; sin embargo, hizo lo siguiente mejor e iteró eso condicional a través de cada fila en mi tabla.Editar: exponiendo mi última oración a petición de John Peter:
IQueryable extiende IEnumerable, por lo que los métodos de IEnumerable como
Where()
obtener sobrecargas que aceptanExpression
. Cuando pasas unExpression
a eso, mantienes un IQueryable como resultado, pero cuando pasas unFunc
, estás volviendo a la base IEnumerable y obtendrás un IEnumerable como resultado. En otras palabras, sin darse cuenta, ha convertido su conjunto de datos en una lista para ser iterada en lugar de algo para consultar. Es difícil notar una diferencia hasta que realmente miras bajo el capó las firmas.fuente
Una consideración extremadamente importante en la elección de Expression vs Func es que los proveedores IQueryable como LINQ to Entities pueden 'digerir' lo que pasa en una Expresión, pero ignorarán lo que pasa en un Func. Tengo dos publicaciones de blog sobre el tema:
Más sobre Expresión vs Func con Entity Framework y caer en el amor con LINQ - Parte 7: Expresiones y Funcs (la última sección)
fuente
Me gustaría agregar algunas notas sobre las diferencias entre
Func<T>
yExpression<Func<T>>
:Func<T>
es simplemente un Delegado de multidifusión de la vieja escuela normal;Expression<Func<T>>
es una representación de la expresión lambda en forma de árbol de expresión;Func<T>
;ExpressionVisitor
;Func<T>
;Expression<Func<T>>
.Hay un artículo que describe los detalles con ejemplos de código:
LINQ: Func <T> vs. Expression <Func <T>> .
Espero que sea de ayuda.
fuente
Hay una explicación más filosófica al respecto del libro de Krzysztof Cwalina ( Directrices de diseño del marco: convenciones, modismos y patrones para bibliotecas .NET reutilizables );
Editar para la versión sin imagen:
fuente
database.data.Where(i => i.Id > 0)
ser ejecutado comoSELECT FROM [data] WHERE [id] > 0
. Si sólo tiene que pasar en un Func, usted ha puesto anteojeras de su conductor y todo lo que puede hacer esSELECT *
y luego una vez que se carga todos los datos en la memoria, iterar a través de cada uno y filtrar todo con id> 0. Envolviendo suFunc
enExpression
empodera el controlador para analizarFunc
y convertirlo en una consulta SQL / MongoDb / other.Expression
pero cuando esté de vacaciones seráFunc/Action
;)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 RPC de protobuf-net (para evitar la generación de código, etc.), por lo que llama a un método con:
Esto deconstruye el árbol de expresión para resolver
SomeMethod
(y el valor de cada argumento), realiza la llamada RPC, actualiza anyref
/out
args y devuelve el resultado de la llamada remota. Esto solo es posible a través del árbol de expresión. Cubro esto más aquí .Otro ejemplo es cuando está construyendo los árboles de expresión manualmente con el propósito de compilar en una lambda, como lo hace el código de operadores genéricos .
fuente
Usaría una expresión cuando desee 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 ve la necesidad de expresiones, probablemente no necesite usar una.
fuente
La razón principal es cuando no desea ejecutar el código directamente, sino que desea inspeccionarlo. Esto puede ser por varias razones:
fuente
Expression
puede ser tan imposible de serializar como un delegado, ya que cualquier expresión puede contener una invocación de una referencia arbitraria de delegado / método. "Fácil" es relativo, por supuesto.Todavía no veo ninguna respuesta que mencione el rendimiento. Pasando
Func<>
s enWhere()
oCount()
es malo. Realmente malo. Si usa un,Func<>
entonces llama a lasIEnumerable
cosas LINQ en lugar deIQueryable
, lo que significa que las tablas completas 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.fuente