Expression\n\nImagina: estás escribiendo código C#. Necesitas pasar una condición de filtrado a un método. ¿Qué elegir: Func<User, bool> o Expression<Func<User, bool>>? Parece que la diferencia es mínima: ambos reciben un usuario y devuelven un bool. Pero entre bastidores se esconde todo un universo de diferencias.
\n\nEn Stack Overflow, esta pregunta ha acumulado miles de visitas, y con razón. Hoy entenderemos por qué Expression<Func<T>> puede ser más potente, rápido y seguro que un Func<T> común. Y lo más importante: cuándo usar cada uno para no dispararte en el pie.
\n\nMini curso intensivo: ¿qué son Func y Expression?
\n\nFunc<T> es simplemente un delegado. Una función lista para ejecutar, código máquina compilado. Le pasas datos y los procesa. Rápido, eficiente, pero... opaco.
\n\nFunc<User, bool> isAdult = user => user.Age >= 18;\nbool result = isAdult(someUser); // se ejecuta al instante
\n\nExpression<Func<T>> es un árbol de expresión. No es código, sino su descripción. No puedes simplemente invocarlo: necesitas compilarlo en un delegado (con .Compile()) o analizar su estructura.
\n\nExpression<Func<User, bool>> expr = user => user.Age >= 18;\n// No se puede invocar directamente: expr(someUser) — error de compilación\nvar compiled = expr.Compile();\nbool result = compiled(someUser); // ahora sí se puede
\n\nPodría parecer un rodeo innecesario, pero es precisamente este carácter "descriptivo" el que abre la puerta a la magia.
\n\nEscenario 1: LINQ to Objects (trabajo en memoria)
\n\nCuando trabajas con colecciones en memoria (por ejemplo, List<T>), LINQ to Objects usa Func<T>. Aquí no se necesitan árboles de expresión: todo se ejecuta en el mismo proceso.
\n\nvar users = new List<User>();\nvar adults = users.Where(u => u.Age >= 18).ToList(); // Func<User, bool>
\n\nEl rendimiento es excelente: sin sobrecarga por analizar el árbol. Si solo trabajas con datos locales, usa Func sin dudar. Es más rápido y sencillo.
\n\nEscenario 2: LINQ to SQL / Entity Framework (trabajo con BD)
\n\nY aquí es donde comienza la magia. Cuando escribes:
\n\nvar adults = dbContext.Users.Where(u => u.Age >= 18).ToList();
\n\nEntity Framework no ejecuta tu código en C#. En su lugar, analiza el árbol de expresión pasado y lo transforma en una consulta SQL:
\n\nSELECT * FROM Users WHERE Age >= 18
\n\nSi hubieras pasado un Func, EF no podría "mirar dentro": simplemente descargaría TODOS los usuarios de la tabla y realizaría el filtrado en memoria. Imagina: una tabla de 10 millones de registros viajando por la red a tu aplicación para filtrarse en el cliente.
\n\nExpression<Func<T>> permite a EF (o a cualquier proveedor IQueryable) leer tu condición, traducirla al lenguaje de la base de datos (SQL, consulta MongoDB, OData, etc.) y ejecutar el filtrado en el servidor.
\n\nRendimiento: benchmarks y cifras reales
\n\nVeamos las cifras. Tomemos un ejemplo simple: filtrar 100 000 usuarios por edad.
\n\n// Opción 1: Func — cargamos todos, filtramos en memoria\nvar allUsers = dbContext.Users.ToList(); // SELECT * FROM Users\nvar adults1 = allUsers.Where(u => u.Age >= 18).ToList();\n\n// Opción 2: Expression — filtramos en el servidor\nvar adults2 = dbContext.Users.Where(u => u.Age >= 18).ToList(); // SELECT * FROM Users WHERE Age >= 18
\n\nResultados (aproximados, pero ilustrativos):
\n\n
Imagina: estás escribiendo código C#. Necesitas pasar una condición de filtrado a un método. ¿Qué elegir: Func<User, bool> o Expression<Func<User, bool>>? Parece que la diferencia es mínima: ambos reciben un usuario y devuelven un bool. Pero entre bastidores se esconde todo un universo de diferencias.
En Stack Overflow, esta pregunta ha acumulado miles de visitas, y con razón. Hoy entenderemos por qué Expression<Func<T>> puede ser más potente, rápido y seguro que un Func<T> común. Y lo más importante: cuándo usar cada uno para no dispararte en el pie.
\n\nMini curso intensivo: ¿qué son Func y Expression?
\n\nFunc<T> es simplemente un delegado. Una función lista para ejecutar, código máquina compilado. Le pasas datos y los procesa. Rápido, eficiente, pero... opaco.
\n\nFunc<User, bool> isAdult = user => user.Age >= 18;\nbool result = isAdult(someUser); // se ejecuta al instante\n\nExpression<Func<T>> es un árbol de expresión. No es código, sino su descripción. No puedes simplemente invocarlo: necesitas compilarlo en un delegado (con .Compile()) o analizar su estructura.
Expression<Func<User, bool>> expr = user => user.Age >= 18;\n// No se puede invocar directamente: expr(someUser) — error de compilación\nvar compiled = expr.Compile();\nbool result = compiled(someUser); // ahora sí se puede\n\nPodría parecer un rodeo innecesario, pero es precisamente este carácter "descriptivo" el que abre la puerta a la magia.
\n\nEscenario 1: LINQ to Objects (trabajo en memoria)
\n\nCuando trabajas con colecciones en memoria (por ejemplo, List<T>), LINQ to Objects usa Func<T>. Aquí no se necesitan árboles de expresión: todo se ejecuta en el mismo proceso.
var users = new List<User>();\nvar adults = users.Where(u => u.Age >= 18).ToList(); // Func<User, bool>\n\nEl rendimiento es excelente: sin sobrecarga por analizar el árbol. Si solo trabajas con datos locales, usa Func sin dudar. Es más rápido y sencillo.
\n\nEscenario 2: LINQ to SQL / Entity Framework (trabajo con BD)
\n\nY aquí es donde comienza la magia. Cuando escribes:
\n\nvar adults = dbContext.Users.Where(u => u.Age >= 18).ToList();\n\nEntity Framework no ejecuta tu código en C#. En su lugar, analiza el árbol de expresión pasado y lo transforma en una consulta SQL:
\n\nSELECT * FROM Users WHERE Age >= 18\n\nSi hubieras pasado un Func, EF no podría "mirar dentro": simplemente descargaría TODOS los usuarios de la tabla y realizaría el filtrado en memoria. Imagina: una tabla de 10 millones de registros viajando por la red a tu aplicación para filtrarse en el cliente.
\n\nExpression<Func<T>> permite a EF (o a cualquier proveedor IQueryable) leer tu condición, traducirla al lenguaje de la base de datos (SQL, consulta MongoDB, OData, etc.) y ejecutar el filtrado en el servidor.
\n\nRendimiento: benchmarks y cifras reales
\n\nVeamos las cifras. Tomemos un ejemplo simple: filtrar 100 000 usuarios por edad.
\n\n// Opción 1: Func — cargamos todos, filtramos en memoria\nvar allUsers = dbContext.Users.ToList(); // SELECT * FROM Users\nvar adults1 = allUsers.Where(u => u.Age >= 18).ToList();\n\n// Opción 2: Expression — filtramos en el servidor\nvar adults2 = dbContext.Users.Where(u => u.Age >= 18).ToList(); // SELECT * FROM Users WHERE Age >= 18\n\nResultados (aproximados, pero ilustrativos):
\n- \n