1. Ventajas de las expresiones lambda
En programación las tareas parecidas se repiten a menudo. Por ejemplo: "filtra la lista de usuarios mayores de 18 años", "suma todos los números que cumplen una condición", "ordena los productos por precio". Sin lambdas esas tareas se resolvían creando métodos separados — y eso añade ruido al código, sobre todo si la acción sólo se usa en un sitio. Las lambdas hacen el código más compacto y más cercano a cómo formulamos la tarea mentalmente.
Las expresiones lambda son una herramienta estándar en muchos lenguajes modernos (no sólo en C#), porque permiten "pasar comportamiento" como valor, ya sea un filtro, un handler o una función de transformación.
1. Concisión y brevedad
Las expresiones lambda permiten deshacerse de largas declaraciones de métodos innecesarios o de delegate anónimos cuando escribes un pequeño trozo de funcionalidad "al vuelo". Por ejemplo, así se vería un filtro de lista de números antes de las lambdas:
List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6 };
// Antes de las lambdas:
List<int> evenNumbers = numbers.FindAll(delegate(int x) { return x % 2 == 0; });
// Con expresión lambda:
List<int> evenNumbers2 = numbers.FindAll(x => x % 2 == 0);
El resultado es el mismo, pero el código con lambda es mucho más compacto. En proyectos grandes el ahorro de líneas de código se vuelve significativo.
2. Mejora de la legibilidad y expresividad
Las lambdas permiten centrarse en la esencia de la operación, quitando el "ruido" del sintaxis. Tu código se acerca más al lenguaje natural:
var adults = users.Where(user => user.Age >= 18);
Compáralo con declarar un método separado bool IsAdult(User user), que tendrías que escribir sólo para ese filtro.
3. Integración cómoda con LINQ y APIs de colecciones
La fuerza principal de las lambdas está en combinación con LINQ y colecciones. Muchos métodos de colecciones y operadores LINQ esperan una función como parámetro (por ejemplo, Func<T, bool> para filtrado). La lambda permite declarar esa función in situ:
var expensive = products.Where(p => p.Price > 1000);
var firstBook = books.FirstOrDefault(b => b.Title.StartsWith("C#"));
var doubled = numbers.Select(n => n * 2);
4. Captura de variables del ámbito externo (closures)
Una expresión lambda puede usar variables declaradas fuera. Eso da flexibilidad y permite crear funciones dinámicas al vuelo:
int minAge = 18;
var filtered = users.Where(u => u.Age >= minAge); // minAge "capturada" por la lambda
Esto abre patrones interesantes para generar funciones con "parámetros configurados".
Dato interesante: Dentro de una lambda no sólo se puede leer, sino a veces modificar variables externas, aunque hay que hacerlo con cuidado — más sobre esto en la lección sobre closures.
5. Código embebido y contextual
Las expresiones lambda "viven" donde se usan, no se buscan por todo el proyecto entre métodos. Esto acerca el código al principio de "máxima información en mínimo espacio".
En los ejemplos del desarrollo de nuestra app (recordemos, estamos construyendo un mini-sistema de gestión de libros en una biblioteca), supongamos que teníamos esta lista de libros:
public class Book
{
public string Title { get; set; }
public string Author { get; set; }
public int Year { get; set; }
public double Price { get; set; }
}
// En algún lugar del código:
List<Book> books = new List<Book>
{
new Book { Title = "C# 9.0 in a Nutshell", Author = "Skeet", Year = 2022, Price = 350 },
new Book { Title = "CLR via C#", Author = "Richter", Year = 2019, Price = 250 },
// ...
};
// Encontrar todos los libros más caros que 300:
var expensiveBooks = books.Where(b => b.Price > 300).ToList();
Ves la condición de selección justo al lado de la llamada, sin perder tiempo buscando funciones externas en el código.
6. Uso como callbacks, eventos, timers
Las lambdas son perfectas para definir una acción puntual, por ejemplo un handler de evento (callback):
button.Click += (sender, args) => Console.WriteLine("¡Botón pulsado!");
Las expresiones lambda evitan la necesidad de escribir métodos separados si el manejador es muy simple.
7. Extensión de las capacidades de los delegates
Antes, para pasar comportamiento había que declarar métodos nombrados; ahora escribes la función en el lugar:
Timer timer = new Timer(_ => Console.WriteLine("¡Tick!"), null, 0, 1000);
8. Facilitan testing e inyección de dependencias
Con lambdas puedes crear fácilmente implementaciones fake (mock) del comportamiento para tests, sin ensuciar el código principal con utilidades o clases temporales. Por ejemplo, si el constructor acepta un delegate, en el test pasas una lambda con el comportamiento deseado.
2. Principales desventajas de las expresiones lambda
Como cualquier herramienta potente, las expresiones lambda no son perfectas. Veamos qué dificultades y limitaciones pueden causar.
1. Pérdida de legibilidad con anidamiento excesivo
Las lambdas están bien hasta que son demasiadas en un mismo sitio. Lambdas anidadas o largas hacen el código pesado de leer:
var result = items.Select(x => x.Children.Where(y => y.Value > 10)
.Select(z => z.Name.ToUpper())
.ToList());
Añade un par de niveles más — y hola, "rompecabezas popular de lectura de código".
Consejo: Si la lambda supera las 3–4 líneas, sáquela a un método nombrado. No tengas miedo de parecer retrógrado: la legibilidad está por encima de las abreviaturas de moda.
2. Dificultades con el depurado
Las lambdas no son muy amigas del debugger, especialmente si están escritas "en una sola línea" dentro de una cadena de llamadas LINQ. A veces es difícil poner un breakpoint dentro de la lambda o inspeccionar valores en un punto concreto.
Para facilitar el depurado, puedes temporalmente mover el cuerpo de la lambda a un método nombrado o dividir cadenas largas de LINQ en pasos con variables intermedias.
3. Tipos de argumentos y valores de retorno no evidentes
La lambda suele pasarse como delegate (Func<...>, Action<...>, Predicate<T>). A veces no es inmediato saber qué tipos deben tener los parámetros de entrada y el tipo de retorno, sobre todo en métodos genéricos.
Por ejemplo:
Func<int, string, double> myFunc = (a, b) => a + b.Length; // ¡Ups! Devuelve int, pero debería devolver double.
El compilador mostrará un error, pero para un principiante no siempre es fácil ver que la lambda "no encaja en el molde".
4. Problemas con la captura de variables
La captura de variables del ámbito externo (closure) es una ventaja y a la vez un problema. Si usas variables capturadas sin cuidado, puedes obtener resultados inesperados. Por ejemplo, en un bucle:
var actions = new List<Action>();
for (int i = 0; i < 3; i++)
{
actions.Add(() => Console.WriteLine(i));
}
foreach (var action in actions) action();
Muchos esperan la salida 0 1 2, pero obtienen 3 3 3. ¿Por qué? Cuando se ejecutan las lambdas la variable i ya vale 3. La lambda "capturó" la variable en sí, no su valor.
Es un error típico de principiantes; hay una explicación detallada en la documentación oficial. Se soluciona, pero requiere atención.
5. Pérdida de nombres explícitos y problemas para reutilizar
Las lambdas son buenas para acciones de una sola vez. Pero si la misma condición/función se usa en varios sitios, mejor sacar la lógica a un método nombrado. Si no, terminas con duplicación y errores al modificar.
6. Incomodidad para añadir comentarios XML
No puedes adjuntar documentación XML a una lambda para generar ayuda automática (como con los métodos). Los comentarios para lambdas se escriben en el código como comentarios normales.
7. Posibles problemas de rendimiento
En la mayoría de los casos las lambdas no son perceptiblemente más lentas que los métodos normales. Sin embargo, la creación masiva y frecuente de lambdas que capturan variables puede provocar la asignación de objetos adicionales (closures). En escenarios críticos de rendimiento (por ejemplo, en tight loops o servicios de alta carga) merece la pena pensar si no es más barato usar métodos estáticos.
8. No se pueden usar operadores goto, break, continue respecto al bucle externo
Si una lambda está declarada dentro de un bucle, dentro de ella no se puede usar directamente break o continue para afectar al bucle externo — eso es sintácticamente inválido.
9. Una lambda no puede describir todo el comportamiento
Las lambdas no trabajan directamente con atributos, no puedes indicar modificadores de acceso, ni ejecutar ciertas acciones especiales — por ejemplo, declarar funciones locales con nombre.
3. Elegir entre dos males
Cuándo las expresiones lambda son útiles
| Escenario | ¿Lambda — conveniente? | Por qué |
|---|---|---|
| Filtrado/transformación corta | 👍 | Rápido y claro |
| Operaciones anidadas multilnivel | 👎 | Se vuelve ilegible |
| Reutilización (re-use) | 👎 | Mejor extraer a un método |
| Lógica de callback, eventos | 👍 | Compacto |
| Describir lógica de negocio compleja | 👎 | Hace falta nombre + comentarios |
| Trabajo con LINQ | 👍 | Escenario ideal |
Cuándo es mejor renunciar a las lambdas
- Si la lógica es larga y contiene muchas ramas/cálculos.
- Si la lambda hace algo poco evidente para el lector y no tiene explicación.
- Si la función debe tener documentación, usarse en varios sitios o necesitar un nombre descriptivo.
- Si la lambda se usa demasiado profundo en llamadas anidadas — existe riesgo de perder legibilidad.
4. Errores típicos al trabajar con expresiones lambda
Error con la captura de variables en un bucle:
List<Action> actions = new List<Action>();
for (int i = 0; i < 5; i++)
{
actions.Add(() => Console.WriteLine(i));
}
foreach (var act in actions) act(); // ¡Todas imprimirán 5!
Cómo hacerlo bien:
for (int i = 0; i < 5; i++)
{
int captured = i; // capturamos una variable separada
actions.Add(() => Console.WriteLine(captured));
}
Lambda demasiado larga:
books.Where(b => b.Price > 1000 && b.Title.Contains("C#") && b.Author.Length > 4 && bla-bla-bla...);
// El código se volvió ilegible, ¡sácalo a un método!
Usar una lambda cuando hace falta un método con documentación:
Si la función se usa muchas veces o debe estar bien documentada, mejor escribir un método nombrado:
bool IsExpensiveBook(Book book) => book.Price > 1000;
books.Where(IsExpensiveBook);
GO TO FULL VERSION