La idea: una variable que guarda una función#
Un delegado es una variable que, en vez de guardar un número o un texto, guarda una función. Eso te permite pasar comportamiento de un lado a otro: almacenarlo, pasarlo como parámetro y ejecutarlo más tarde. Es la base de los eventos, de LINQ y de muchos patrones limpios.
Action y Func: los delegados listos para usar#
Casi nunca necesitas declarar un delegado a mano: C# trae dos genéricos que cubren todo.
using System;
// Action: NO devuelve nada. El <...> son los parámetros que recibe.
Action saludar = () => Debug.Log("Hola");
Action<int> mostrarDaño = (n) => Debug.Log("Daño: " + n);
Action<string, int> log = (msg, code) => Debug.Log(msg + " " + code);
// Func: SÍ devuelve. El ÚLTIMO tipo del <...> es lo que devuelve.
Func<int> dado = () => UnityEngine.Random.Range(1, 7);
Func<int, int, int> sumar = (a, b) => a + b;
// Ejecutarlos:
saludar(); // Hola
mostrarDaño(10); // Daño: 10
int total = sumar(2, 3); // 5Action<int, string> → recibe un int y un string, no devuelve nada. Func<int, string, bool> → recibe un int y un string, y devuelve un bool (el último tipo siempre es el retorno). Regla: Action = hacer (void), Func = calcular (devuelve algo).
Lambdas: funciones sin nombre#
Ese (a, b) => a + b es una expresión lambda: una función anónima, escrita en el sitio. => se lee "va a". Son comodísimas para funciones pequeñas que solo usas una vez:
// En vez de declarar un método aparte:
enemigos.Where(e => e.Vida > 0); // lambda como filtro (LINQ)
boton.onClick.AddListener(() => Debug.Log("clic"));
Invoke(() => Reaparecer(), 2f);También puedes pasar un método con nombre donde se espera un delegado (sin paréntesis, sin ejecutarlo):
boton.onClick.AddListener(Jugar); // pasa el método Jugar, no lo llama
void Jugar() => Debug.Log("¡A jugar!");Callbacks: "avísame cuando termines"#
Un uso clásico: pasar una función que se ejecutará cuando algo acabe. Es el patrón callback.
public void CargarNivel(string nombre, Action alTerminar)
{
// ...carga...
alTerminar?.Invoke(); // avisa a quien lo pidió
}
// Uso:
CargarNivel("Nivel1", () => Debug.Log("Nivel cargado, arranca la música"));alTerminar?.Invoke() significa "si hay una función asignada, ejecútala; si es null, no hagas nada". Sin el ?., invocar un delegado nulo lanza NullReferenceException. Es el mismo patrón que en los eventos.
Multicast: varias funciones en un delegado#
Un delegado puede guardar varias funciones a la vez con +=. Al invocarlo, se ejecutan todas. Esto es justo lo que hace posible que muchos escuchen un evento:
Action alMorir = null;
alMorir += MostrarGameOver;
alMorir += GuardarPuntuacion;
alMorir += ReproducirSonido;
alMorir?.Invoke(); // ejecuta las tresDelegados vs eventos#
Un event (ver eventos) es un delegado con protección: desde fuera solo puedes suscribirte (+=) o darte de baja (-=), no invocarlo ni borrar los suscriptores de otros. Para comunicación entre objetos, usa event; para pasar una función puntual (un callback, un filtro), un Action/Func normal.
Errores frecuentes#
- Invocar un delegado nulo sin
?.(excepción). - Confundir pasar el método (
Jugar) con llamarlo (Jugar()) al asignarlo a un delegado. - Suscribir lambdas a eventos y no poder darse de baja (una lambda anónima no se puede quitar con
-=; usa un método con nombre si necesitas desuscribir). - Liarse con Action/Func: recuerda, Func devuelve (último tipo), Action no.
Ponte a prueba#
Resumen#
Un delegado es una variable que guarda una función. Usa Action (no devuelve) y Func (devuelve, último tipo) en vez de declararlos a mano, y lambdas (x => ...) para funciones pequeñas en línea. Sirven para callbacks ("avísame al terminar") y, en versión multicast (+=), sostienen los eventos. Invoca siempre con ?.Invoke() y recuerda: las lambdas anónimas no se pueden desuscribir.