1. Introducción
Puede que no lo supieras, pero una de las preguntas favoritas en entrevistas es: “¿Qué sabes sobre las expresiones lambda?”. ¿Por qué la preguntan? Porque es una cosa muy cómoda y "de moda" en C#, que permite escribir menos código y hacerlo más expresivo. Si los métodos anónimos son como escribir una carta a mano, las expresiones lambda son como enviar un mensaje por un messenger: rápido, compacto y a veces hasta con emoticonos (bueno, casi).
Las expresiones lambda llegaron a C# con la versión 3.0 y desde entonces se han propagado por todas partes: en LINQ, manejadores de eventos, hilos, colecciones e incluso bibliotecas de inteligencia artificial en .NET.
Una expresión lambda es una forma de declarar un método directamente en el cuerpo del código, "in situ", sin nombre, súper corta y clara. La sintaxis básica siempre se ve así:
(parámetros) => expresión_o_bloque_de_código
Esta "flecha" => se llama “operador lambda” o “flecha”.
Pequeña analogía
Recuerda cómo escribes una receta: “Tomar una manzana, cortarla, ponerla en un bol”.
En C# sería:
(manzana) => cortar(manzana)
2. Convertimos un método anónimo a una lambda
Supongamos que tenemos un delegado:
delegate int SquareDelegate(int x);
Un método anónimo se vería así:
SquareDelegate sq = delegate(int x) {
return x * x;
};
Y ahora lo mismo con una expresión lambda:
SquareDelegate sq = (int x) => { return x * x; };
Pero C# puede inferir los tipos, y podemos acortarlo todavía más:
SquareDelegate sq = x => x * x;
¡Qué compacta y genial!
Comparación con un método "normal" y una función anónima
| Método | Declaración | Dónde usar | Contras |
|---|---|---|---|
| Método nombrado | En la clase | En cualquier sitio | Necesita nombre, más código |
| Método anónimo | En el código | Sólo con delegados | Sintaxis más verbosa |
| Expresión lambda | En el código | En cualquier sitio con delegados | A veces el tipo no es obvio |
3. Sintaxis de la expresión lambda: variaciones
Parámetros
Sin parámetros:
Action hello = () => Console.WriteLine("¡Hola, mundo!");
Un parámetro (puedes omitir los paréntesis):
Func<int, int> inc = x => x + 1;
Varios parámetros (se necesitan paréntesis):
Func<int, int, int> sum = (a, b) => a + b;
Cuerpo de la lambda
Una sola expresión — sin llaves y sin return:
x => x * x
Bloque de código — llaves y necesitas return:
(x, y) =>
{
int z = x + y;
return z * z;
}
Tipos de parámetros
La mayoría de las veces no hace falta especificar el tipo — el compilador lo infiere. Pero si quieres, puedes:
(x, y) => x + y // El compilador infiere los tipos.
(int x, int y) => x + y // Puedes especificarlos explícitamente.
4. Matices y ejemplos más reales
Uso con colecciones
Recuerda nuestra mini-aplicación de ejemplo (por ejemplo, una lista de usuarios). Supongamos que tenemos un array de números:
int[] numbers = { 1, 2, 3, 4, 5 };
Queremos seleccionar solo los números pares:
var evenNumbers = numbers.Where(n => n % 2 == 0);
Aquí Where es un método de extensión de LINQ, y la condición es nuestro filtro lambda.
Pasar a un método
Supongamos que tenemos un método delegado:
public delegate bool Filter(int number);
public static int[] FilterNumbers(int[] data, Filter predicate)
{
var result = new List<int>();
foreach (var n in data)
if (predicate(n))
result.Add(n);
return result.ToArray();
}
Ahora pasamos una expresión lambda:
int[] evens = FilterNumbers(numbers, n => n % 2 == 0);
Lambda como manejador de eventos
button.Click += (sender, args) => Console.WriteLine("¡Botón pulsado!");
5. Lambdas y los delegados genéricos estándar
Es difícil imaginar las lambdas sin Func<>, Action<> y Predicate<>. Son tipos especiales de delegados:
- Action — no devuelve nada.
- Func — devuelve un valor.
- Predicate — devuelve bool, normalmente para filtrado.
Ejemplo con Action:
Action<string> log = message => Console.WriteLine(message);
log("¡Este mensaje a través de una lambda!");
Ejemplo con Func:
Func<int, int, int> multiply = (a, b) => a * b;
int product = multiply(3, 5); // 15
Ejemplo con Predicate:
Predicate<int> isNegative = n => n < 0;
bool test = isNegative(-7); // true
6. Lambdas con múltiples expresiones: cuándo usar un bloque
Si la lambda debe ejecutar varias operaciones, usa llaves y un return explícito:
Func<int, int, string> describeSum = (a, b) =>
{
int sum = a + b;
return $"Suma: {sum}";
};
Sin return el compilador se quejará (pero no siempre explica bien por qué — simplemente dirá que "no todos los caminos devuelven un valor").
7. Devolver void: Action
Cuando la lambda no devuelve nada, usa Action:
Action greet = () => Console.WriteLine("¡Sonríe — el código compila!");
La lambda puede contener tantas instrucciones como quieras:
Action<int> printSquare = x =>
{
int sq = x * x;
Console.WriteLine($"El cuadrado de {x} es {sq}");
};
8. Limitaciones y errores típicos
A veces quieres escribir una lambda muy libremente, pero el compilador no siempre está de acuerdo.
No puedes declarar una variable con el mismo nombre que una variable capturada desde el exterior.
Hay que vigilar los tipos: si la firma del delegado y la lambda no coinciden — habrá error.
El retorno de valor es obligatorio si el delegado requiere un valor de retorno.
En lambdas complejas no olvides las llaves. Una sola expresión — sin llaves y sin return. Varias instrucciones — llaves y return.
GO TO FULL VERSION