CodeGym /Cursos /C# SELF /Expresiones lambda en colecciones y

Expresiones lambda en colecciones y LINQ

C# SELF
Nivel 49 , Lección 2
Disponible

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
Where
Filtra elementos
Func<T, bool>
p => p.Price > 100
Select
Proyecta, transforma
Func<T, U>
p => p.Name
OrderBy
Ordena por clave
Func<T, K>
u => u.Age
FirstOrDefault
Primer elemento que cumple condición
Func<T, bool>
u => u.Username == "Bob"
Any
¿Existe al menos un elemento que cumple condición?
Func<T, bool>
u => u.Age < 18
All
¿Todos los elementos cumplen la condición?
Func<T, bool>
u => u.Age >= 18
Count
Número de elementos que cumplen condición
Func<T, bool>
p => p.Price > 50
ForEach
Hacer algo con cada elemento
Action<T>
u => Console.WriteLine(u.Name)
RemoveAll
Elimina todos los que cumplen el predicado
Predicate<T>
u => u.Age < 18

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.

Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION