1. Introducción
En proyectos reales casi la mitad de los programadores pasa buena parte de su vida trabajando con colecciones: filtran, cuentan, buscan, reordenan, meten, sacan — en resumen, las tratan casi como la nevera antes de dormir. Especialmente a menudo hay que extraer, procesar y agregar datos — sean listas de usuarios, productos en un catálogo, líneas de texto o cualquier otro array.
Casi todas las colecciones modernas en .NET soportan métodos funcionales — como Where, Select, Find, Any, All y otros. Su fuerza está en la versatilidad y el estilo conciso: simplemente pasas un "pedacito de lógica" como una expresión lambda, y la colección cobra vida, como si hubieras arrancado un motor nuevo.
LINQ (Language Integrated Query) — no es sólo azúcar sintáctico, es todo un mini-lenguaje dentro de C#, que te permite escribir consultas a datos como si usaras SQL o Excel. Pero mejor: directamente en el código, con autocompletado, tipos y debugger.
Pero toda esa magia funciona gracias a los delegados — y escribir cada vez un método separado sólo para filtrar un array resulta cansino. Ahí las expresiones lambda entran en juego como mini-funciones "in situ", transformando código pesado en algo elegante y expresivo.
2. Expresiones lambda en métodos estándar de colecciones
Las expresiones lambda brillan especialmente en los métodos estándar de colecciones basados en delegados, como Find, Exists, ForEach y muchos otros.
Ejemplo: Búsqueda por condición
Supongamos que tienes una lista de productos:
using System;
using System.Collections.Generic;
// Nuestro clase Product
public class Product
{
public string Name { get; set; }
public int Price { get; set; }
}
var products = new List<Product>
{
new Product { Name = "Kofe", Price = 100 },
new Product { Name = "Chai", Price = 70 },
new Product { Name = "Moloko", Price = 80 }
};
// Encontramos el primer producto caro (>90)
Product expensive = products.Find(p => p.Price > 90); // ¡Usamos lambda!
Console.WriteLine(expensive?.Name); // => Kofe
Sin la lambda habrías tenido que escribir un método separado o una función anónima al estilo antiguo. Así — una línea y el código se lee como inglés: "Find product where price > 90".
Ejemplo: Comprobar existencia de producto
bool hasCheap = products.Exists(p => p.Price < 75);
Console.WriteLine(hasCheap); // => True (porque "Chai" es más barato que 75)
Ejemplo: Procesar todos los elementos (ForEach)
A veces hay que hacer algo con cada elemento:
products.ForEach(p => Console.WriteLine($"{p.Name}: {p.Price} euro"));
Sobre analogías
En resumen: las expresiones lambda en colecciones son como el botón "mejorar" en un editor de fotos. Pulsas — ¡y listo!
3. Expresiones lambda y LINQ: magia para colecciones
LINQ no es sólo comodidad, también es una gran introducción al estilo funcional. La mayoría de los métodos de LINQ esperan delegados — así que su compañero ideal y cómplice son las expresiones lambda.
Filtrado con Where
Volvamos a la lista de productos. Vamos a seleccionar sólo los productos "baratos":
using System.Linq;
var cheapProducts = products.Where(p => p.Price < 90);
foreach (var p in cheapProducts)
Console.WriteLine(p.Name); // Chai, Moloko
Obtenemos una nueva colección sin escribir ningún bucle manual. Where acepta una lambda-predicado (función que devuelve true/false), y la aplica a cada elemento.
Ordenación con OrderBy
Si te gusta el orden — aquí:
var sorted = products.OrderBy(p => p.Price);
foreach (var p in sorted)
Console.WriteLine($"{p.Name}: {p.Price}");
// Chai: 70
// Moloko: 80
// Kofe: 100
Mapeo (Select) – proyección de datos
A veces no necesitamos todo el objeto, sino sólo una parte, por ejemplo, la lista de nombres:
var names = products.Select(p => p.Name);
foreach (var name in names)
Console.WriteLine(name); // Kofe, Chai, Moloko
Cadenas de LINQ
LINQ mola porque puedes encadenar llamadas:
var namesOfCheap = products
.Where(p => p.Price < 90)
.OrderBy(p => p.Name)
.Select(p => p.Name.ToUpper());
foreach (var name in namesOfCheap)
Console.WriteLine(name); // MOLOKO, CHAI
Se parece a una cadena de producción: cada método es una etapa de procesamiento.
Pregunta: ¿Por qué las lambdas son mejores que métodos normales para LINQ?
Primero, las lambdas se escriben en el sitio donde se necesitan. Segundo, son cortas y legibles. Tercero, es el estándar del C# moderno — casi todo el mundo escribe así, y los que no suelen tener problemas en entrevistas.
4. Ejemplo práctico
Durante el curso escribimos una app educativa para manejar un catálogo pequeño de productos, usuarios u órdenes. Añadamos métodos modernos de procesamiento de colecciones.
Búsqueda de usuario por nombre
public class User
{
public string Username { get; set; }
public int Age { get; set; }
}
var users = new List<User>
{
new User{ Username = "Alice", Age = 21 },
new User{ Username = "Bob", Age = 26 },
new User{ Username = "Charlie", Age = 32 }
};
// Buscar usuario por nombre
User found = users.FirstOrDefault(u => u.Username == "Bob");
Console.WriteLine(found?.Age); // 26
Filtrado por edad
var adults = users.Where(u => u.Age >= 18);
foreach (var u in adults)
Console.WriteLine(u.Username); // Alice, Bob, Charlie
Contar usuarios
int count = users.Count(u => u.Age > 25);
Console.WriteLine(count); // 2 (Bob y Charlie)
Comprobar si todos son adultos
bool allAdults = users.All(u => u.Age >= 18);
Console.WriteLine(allAdults); // True
¿Hay al menos un menor?
bool hasMinor = users.Any(u => u.Age < 18);
Console.WriteLine(hasMinor); // False
5. LINQ: Cómo funciona por dentro
Cuando escribes, por ejemplo, Where(u => u.Age > 20), en realidad es casi lo mismo que crear un bucle que recorre todos los elementos y comprueba la condición para cada uno. Sólo que LINQ lo hace invisible y bonito, envolviendo tu predicado en un delegado.
Si no existieran las lambdas, tendrías que montar construcciones así:
public static bool AgeMoreThan20(User u) => u.Age > 20;
var adultUsers = users.Where(AgeMoreThan20);
O métodos anónimos al estilo "old-school":
var adultUsers = users.Where(delegate(User u) { return u.Age > 20; });
Todo eso es más verboso y aburrido. Con lambda — elegante y moderno.
6. Delegados y tipos estándar: Func, Action, Predicate
No sólo LINQ ama las lambdas. Muchos métodos de colecciones aceptan delegados especializados, por ejemplo:
- Predicate<T> — para métodos Find, Exists, RemoveAll
- Func<T, TResult> — para métodos LINQ, proyecciones, cálculos
- Action<T> — para métodos que hacen algo con el elemento pero no devuelven nada (ForEach)
Así se ve en práctica:
// Predicate<T>
users.RemoveAll(u => u.Age < 30); // Eliminamos a todos los menores de 30
// Func<T, TResult>
var names = users.Select(u => u.Username);
// Action<T>
users.ForEach(u => Console.WriteLine(u.Username));
7. Chuleta de métodos de colecciones con expresiones lambda
| Método | Qué hace | Tipo de delegado | Ejemplo de lambda |
|---|---|---|---|
|
Filtra elementos | |
|
|
Proyecta, transforma | |
|
|
Ordena por clave | |
|
|
Primer elemento que cumple condición | |
|
|
¿Existe al menos un elemento que cumple condición? | |
|
|
¿Todos los elementos cumplen la condición? | |
|
|
Número de elementos que cumplen condición | |
|
|
Hacer algo con cada elemento | |
|
|
Elimina todos los que cumplen el predicado | |
|
8. Errores típicos y particularidades
Uno de los errores más comunes — olvidar que LINQ no modifica la colección original, sino que devuelve una nueva secuencia. Es decir, después de var sorted = users.OrderBy(u => u.Age); la colección users seguirá en el orden original. Eso confunde: a veces parece que ya está ordenada, pero no.
Otro punto: métodos como Where, Select y otros devuelven objetos de tipo IEnumerable<T>. Esa colección es "perezosa" — el procesamiento real se hace cuando realmente enumeras (foreach, ToList(), etc.). Si quieres materializar el resultado, no olvides llamar a ToList() o ToArray():
var sortedList = users.OrderBy(u => u.Age).ToList();
También recuerda: si una lambda cierra sobre variables externas (closures), esas variables viven en memoria mientras la lambda exista. No es grave, pero si usas una lambda dentro de un objeto de larga vida y capturas un "array enorme", ese array se quedará en memoria junto con la lambda.
Y además: usa nombres descriptivos para parámetros y variables — mejora mucho la legibilidad, especialmente con varias lambdas anidadas.
GO TO FULL VERSION