Módulo 15: Delegados, eventos y lambdas
Objetivos del módulo
- Entender qué son los delegados y cómo usarlos
- Dominar
Action,FuncyPredicate - Crear y suscribirse a eventos
- Escribir expresiones lambda
1. ¿Qué es un delegado?
📘 Concepto: Un delegado es un tipo que representa una referencia a un método. Es como una variable que almacena una función. Permite pasar funciones como parámetros a otros métodos.
// 1. Definir el tipo delegado (firma del método)
delegate double Operacion(double a, double b);
// 2. Métodos que coinciden con la firma del delegado
static double Sumar(double a, double b) => a + b;
static double Restar(double a, double b) => a - b;
static double Multiplicar(double a, double b) => a * b;
static double Dividir(double a, double b) => b != 0 ? a / b : 0;
// 3. Usar el delegado
Operacion op = Sumar; // Asignar un método al delegado
Console.WriteLine(op(10, 3)); // 13
op = Multiplicar; // Cambiar el método asignado
Console.WriteLine(op(10, 3)); // 30
// Pasar como parámetro
static double Calcular(double a, double b, Operacion operacion)
{
return operacion(a, b);
}
Console.WriteLine(Calcular(10, 5, Sumar)); // 15
Console.WriteLine(Calcular(10, 5, Restar)); // 5
Console.WriteLine(Calcular(10, 5, Dividir)); // 2
2. Delegados predefinidos: Action, Func, Predicate
En la práctica, casi nunca defines tus propios delegados. C# incluye 3 delegados genéricos que cubren casi todos los casos:
Action: método sin retorno (void)
// Action sin parámetros
Action saludar = () => Console.WriteLine("¡Hola!");
saludar(); // ¡Hola!
// Action con parámetros
Action<string> saludarA = nombre => Console.WriteLine($"Hola, {nombre}!");
saludarA("Ana"); // Hola, Ana!
// Action con múltiples parámetros
Action<string, int> mostrarInfo = (nombre, edad) =>
Console.WriteLine($"{nombre} tiene {edad} años");
mostrarInfo("Luis", 30);
Func: método con retorno
// Func<retorno>
Func<int> obtenerNumero = () => 42;
Console.WriteLine(obtenerNumero()); // 42
// Func<param, retorno> - el ÚLTIMO tipo es siempre el retorno
Func<int, int, int> sumar = (a, b) => a + b;
Console.WriteLine(sumar(5, 3)); // 8
// Func<param, retorno>
Func<string, int> longitud = texto => texto.Length;
Console.WriteLine(longitud("Hola")); // 4
// Func<param1, param2, retorno>
Func<double, double, double> potencia = Math.Pow;
Console.WriteLine(potencia(2, 10)); // 1024
Predicate: método que devuelve bool
// Predicate es equivalente a Func<T, bool>
Predicate<int> esPar = n => n % 2 == 0;
Console.WriteLine(esPar(4)); // True
Console.WriteLine(esPar(7)); // False
// Muy útil con métodos de colecciones
List<int> numeros = new() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
List<int> pares = numeros.FindAll(esPar);
Console.WriteLine(string.Join(", ", pares)); // 2, 4, 6, 8, 10
numeros.RemoveAll(n => n > 7);
Console.WriteLine(string.Join(", ", numeros)); // 1, 2, 3, 4, 5, 6, 7
3. Expresiones lambda
📘 Concepto: Una expresión lambda es una forma corta de escribir una función anónima (sin nombre). Usa la sintaxis
=>(“va a” o “produce”).
// Función normal
static int Duplicar(int x)
{
return x * 2;
}
// Lambda equivalente
Func<int, int> duplicar = x => x * 2;
// Lambda con cuerpo (múltiples líneas)
Func<int, int> triplicar = x =>
{
int resultado = x * 3;
return resultado;
};
Sintaxis lambda resumida
| Situación | Ejemplo |
|---|---|
| Sin parámetros | () => Console.WriteLine("Hola") |
| Un parámetro | x => x * 2 |
| Varios parámetros | (a, b) => a + b |
| Con tipos explícitos | (int a, int b) => a + b |
| Multilínea | (a, b) => { var r = a + b; return r; } |
Lambdas con colecciones
Las lambdas son la forma habitual de trabajar con colecciones:
List<string> nombres = new() { "Ana", "Luis", "María", "Pedro", "Carmen" };
// Buscar
string? primeroConM = nombres.Find(n => n.StartsWith("M")); // "María"
List<string> cortosEn4 = nombres.FindAll(n => n.Length <= 4); // "Ana", "Luis"
// Ordenar con criterio personalizado
nombres.Sort((a, b) => a.Length.CompareTo(b.Length)); // Por longitud
// Comprobar
bool hayMayorDe5 = nombres.Exists(n => n.Length > 5); // True (Carmen)
bool todosMasDe2 = nombres.TrueForAll(n => n.Length > 2); // True
// ForEach
nombres.ForEach(n => Console.WriteLine($"- {n}"));
// ConvertAll
List<int> longitudes = nombres.ConvertAll(n => n.Length);
4. Funciones de orden superior
📘 Concepto: Una función de orden superior es una función que recibe o devuelve otra función como parámetro.
// Función que recibe una función como parámetro
static List<T> Filtrar<T>(List<T> lista, Func<T, bool> condicion)
{
List<T> resultado = new();
foreach (T item in lista)
{
if (condicion(item))
resultado.Add(item);
}
return resultado;
}
List<int> numeros = new() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
var pares = Filtrar(numeros, n => n % 2 == 0); // 2, 4, 6, 8, 10
var mayoresQue5 = Filtrar(numeros, n => n > 5); // 6, 7, 8, 9, 10
var multiplos3 = Filtrar(numeros, n => n % 3 == 0); // 3, 6, 9
// Función que DEVUELVE una función
static Func<double, double> CrearMultiplicador(double factor)
{
return x => x * factor;
}
var duplicar = CrearMultiplicador(2);
var triplicar = CrearMultiplicador(3);
Console.WriteLine(duplicar(5)); // 10
Console.WriteLine(triplicar(5)); // 15
5. Eventos
📘 Concepto: Un evento es un mecanismo de notificación: un objeto avisa a otros cuando algo ocurre. Se basa en delegados. El patrón es publicador/suscriptor.
Crear y usar eventos
class Cronometro
{
// Declarar el evento usando EventHandler
public event EventHandler<int>? TicTac;
public void Iniciar(int segundos)
{
for (int i = 1; i <= segundos; i++)
{
Thread.Sleep(1000); // Espera 1 segundo
// Lanzar el evento (notificar a los suscriptores)
TicTac?.Invoke(this, i);
}
}
}
// Programa
var crono = new Cronometro();
// Suscribirse al evento con lambda
crono.TicTac += (sender, segundo) =>
{
Console.WriteLine($"Tic... {segundo} segundos");
};
// Otra suscripción (pueden haber varias)
crono.TicTac += (sender, segundo) =>
{
if (segundo == 3)
Console.WriteLine("¡Ya van 3 segundos!");
};
crono.Iniciar(5);
Evento personalizado con datos
// Datos del evento
class PedidoEventArgs : EventArgs
{
public string NombreProducto { get; set; } = "";
public int Cantidad { get; set; }
public DateTime Fecha { get; set; }
}
class Tienda
{
public event EventHandler<PedidoEventArgs>? NuevoPedido;
public void RealizarPedido(string producto, int cantidad)
{
Console.WriteLine($"Pedido realizado: {cantidad}x {producto}");
// Notificar a los suscriptores
NuevoPedido?.Invoke(this, new PedidoEventArgs
{
NombreProducto = producto,
Cantidad = cantidad,
Fecha = DateTime.Now
});
}
}
// Suscriptores
var tienda = new Tienda();
// Servicio de email
tienda.NuevoPedido += (sender, e) =>
Console.WriteLine($"📧 Email enviado: Pedido de {e.Cantidad}x {e.NombreProducto}");
// Servicio de inventario
tienda.NuevoPedido += (sender, e) =>
Console.WriteLine($"📦 Inventario actualizado: -{e.Cantidad} {e.NombreProducto}");
// Servicio de log
tienda.NuevoPedido += (sender, e) =>
Console.WriteLine($"📋 Log: Pedido a las {e.Fecha:HH:mm:ss}");
tienda.RealizarPedido("Teclado", 2);
// Pedido realizado: 2x Teclado
// 📧 Email enviado: Pedido de 2x Teclado
// 📦 Inventario actualizado: -2 Teclado
// 📋 Log: Pedido a las 14:30:22
6. Ejercicios
Ejercicio 1: Calculadora con delegados
Crea un Dictionary<string, Func<double, double, double>> con operaciones (+, -, *, /, ^). El usuario elige operación y números, y se ejecuta el Func correspondiente.
Ejercicio 2: Pipeline de transformaciones
Crea una función Transformar que reciba un string y una lista de Func<string, string>. Aplica todas las transformaciones en orden. Ejemplo: quitar espacios → mayúsculas → revertir.
Ejercicio 3: Sistema de eventos - Chat
Crea una clase SalaChat con un evento MensajeRecibido. Varios Usuario se suscriben. Cuando un usuario envía un mensaje, todos los demás lo ven.
Ejercicio 4: Filtro configurable
Crea una función genérica FiltrarYTransformar<TInput, TOutput> que reciba una lista, un Predicate<TInput> para filtrar y un Func<TInput, TOutput> para transformar. Ejemplo: filtrar números pares y convertirlos a string.
Resumen
| Concepto | Ejemplo |
|---|---|
| Delegado propio | delegate int Op(int a, int b); |
| Action (void) | Action<string> a = s => Console.Write(s); |
| Func (con retorno) | Func<int, int> f = x => x * 2; |
| Predicate (→ bool) | Predicate<int> p = n => n > 0; |
| Lambda sin params | () => 42 |
| Lambda con params | (a, b) => a + b |
| Evento | public event EventHandler<T>? Evento; |
| Lanzar evento | Evento?.Invoke(this, args); |
| Suscribirse | obj.Evento += (s, e) => { }; |