1. Introducción
Recordemos: un evento es un contrato público, la promesa de tu código de "llamar" a los suscriptores cuando ocurra algo importante. En ejemplos de lecciones anteriores podrías haber visto una declaración así:
public event Action<string> MessageSent;
O incluso:
public delegate void MyHandler(int value);
public event MyHandler SomethingHappened;
Funciona, pero este enfoque provoca un montón de problemas — desde inconsistencias en las firmas hasta la imposibilidad de saber quién disparó el evento y qué pasó exactamente. Imagínate que al pulsar una tecla el comportamiento fuera aleatorio, cada vez distinto dentro de la misma aplicación con la misma tarea. Por ejemplo, estás jugando y pulsas la barra espaciadora. Hace un momento significaba saltar, y ahora de repente abre el inventario. Una pesadilla, no un estándar.
En .NET existe una especie de Constitución para eventos — un convenio sobre la firma del handler y la estructura de la información que se pasa con el evento. Así se ve el estilo canónico:
void Handler(object sender, EventArgs args);
¿Te suena? Aparece por todas partes: desde clicks en WinForms hasta eventos del sistema en ASP.NET y en librerías de terceros.
2. Qué son EventHandler y EventArgs
La idea principal: cada evento comunica dos cosas:
- ¿Quién disparó el evento? sender
- ¿Qué pasó? EventArgs — datos adicionales
En el mundo de C# se expresa así:
public delegate void EventHandler(object sender, EventArgs e);
- object sender — referencia al iniciador del evento. Puede ser cualquier cosa — simplemente this.
- EventArgs e — objeto con información adicional sobre el evento. Para escenarios simples se usa EventArgs.Empty, para casos complejos — heredando EventArgs.
Dato curioso
En las recomendaciones oficiales de .NET para APIs públicas se acepta que un evento tenga la firma (object sender, EventArgs e). Si ves un evento sin sender y EventArgs — probablemente sea una simplificación del autor, no el estilo canónico de .NET.
Gran beneficio de un único estándar
Con un estándar único los eventos son más fáciles de loguear, testear, suscribirse de forma genérica y extender el sistema. Abre el código fuente de WinForms, WPF, ASP.NET — verás el mismo patrón.
3. Cómo usar la plantilla estándar de eventos
1. Usar el delegado integrado EventHandler
En lugar de declarar tu propio delegate puedes usar el que ya existe:
public event EventHandler SmthHappened;
Y ahora el handler siempre tiene la misma forma:
private void OnSmthHappened(object sender, EventArgs e)
{
// Lógica de reacción al evento
}
La suscripción sigue siendo sencilla:
myObj.SmthHappened += OnSmthHappened;
Importante: si el evento no transmite datos adicionales, usa EventArgs.Empty.
2. Crear tus propios argumentos de evento
Si necesitas pasar información (resultado de un cálculo, nombre de archivo, error), crea una clase que herede de EventArgs:
public class CalculationEventArgs : EventArgs
{
public double Result { get; }
public CalculationEventArgs(double result) => Result = result;
}
Después usamos el delegado genérico incorporado — EventHandler<TEventArgs>:
public event EventHandler<CalculationEventArgs> CalculationFinished;
Y el handler ahora recibe exactamente tu tipo de argumentos:
private void OnCalculationFinished(object sender, CalculationEventArgs e)
{
Console.WriteLine($"Cálculo terminado. Resultado: {e.Result}");
}
4. Ejemplo de aplicación
Desarrollamos nuestro proyecto educativo — imaginemos un calculadora que suma o resta números y notifica la finalización de la operación mediante un evento.
Ejemplo minimalista
public class Calculator
{
public event EventHandler<CalculationEventArgs> CalculationPerformed;
public void Add(int a, int b)
{
int result = a + b;
// "Disparamos" el evento
CalculationPerformed?.Invoke(this, new CalculationEventArgs(result));
}
}
public class CalculationEventArgs : EventArgs
{
public int Result { get; }
public CalculationEventArgs(int result) => Result = result;
}
Nos suscribimos y usamos:
var calc = new Calculator();
calc.CalculationPerformed += (sender, e) =>
{
Console.WriteLine($"Resultado de la operación: {e.Result}");
};
calc.Add(10, 20);
// Imprimirá: Resultado de la operación: 30
Ese es todo el "estándar": el handler siempre recibe el objeto remitente y el objeto de argumentos — universal y claro.
5. Novedades útiles
Encapsular la invocación del evento: buen estilo
En .NET es costumbre sacar la lógica de invocar el evento a un método protegido con prefijo On:
protected virtual void OnCalculationPerformed(CalculationEventArgs e)
{
CalculationPerformed?.Invoke(this, e);
}
Y dentro de la lógica ("Add", "Subtract", etc.) simplemente llamas ese método:
public void Add(int a, int b) => OnCalculationPerformed(new CalculationEventArgs(a + b));
Este estilo permite a las clases derivadas sobrescribir el comportamiento del evento y reduce la probabilidad de olvidar invocarlo.
Esquema: Cómo está organizado un evento según el estándar .NET
graph LR
A[Objeto-publicador] -- "event EventHandler/ EventHandler<TEventArgs>" --> B[Lista de suscriptores]
B -- "Método-handler (object sender, EventArgs e)" --> C[Reacción al evento]
A -- "this (remitente)" --> C
A -- "EventArgs (datos)" --> C
Comparación de variantes para declarar eventos
| Enfoque | Transmisión del iniciador (sender) | Transmisión de argumentos | Universalidad | Uso en .NET |
|---|---|---|---|---|
|
No | Sí | Baja | No |
|
Sí | Sí | Media | No (raro) |
|
Sí | No (EventArgs) | Alta | Sí, estándar |
|
Sí | Sí (MyArgs) | Muy alta | Sí, estándar |
Utilidad práctica en entrevistas y código real
Usar la plantilla estándar de eventos es un must-have para un desarrollador .NET. En una entrevista casi seguro te preguntarán sobre EventHandler y el par sender/EventArgs. Un evento tipo Action<T> suele verse como una "simplificación".
En proyectos reales este enfoque simplifica el trabajo en equipo, testing, extensibilidad y mantenimiento. Librerías externas (logging, profilers) se integran más fácilmente cuando se usa la forma estándar.
Diagrama de flujo de la invocación del evento
flowchart TD
subgraph A[Clase-publicadora]
C1((Objeto))
C2[Método que invoca el evento]
C3["event EventHandler<MyArgs>"]
end
subgraph B[Clase-suscriptora]
D1((Suscripción))
D2["Handler del evento (object sender, MyArgs args)"]
end
C1 -- Llama --> C2
C2 -- "Invoke(this, args)" --> C3
C3 -- "Notifica" --> D1
D1 -- "Ejecuta" --> D2
6. Consejos, matices y errores típicos
1. Firma del handler. ¿Te tienta hacer un evento tipo Action<int>? Es tentador, pero pierdes el sender y la compatibilidad con el ecosistema.
2. Transmisión de argumentos. No confundas EventArgs con parámetros ordinarios. Pasa todos los datos del handler dentro del objeto de argumentos.
3. Uso de null para argumentos. En lugar de null usa EventArgs.Empty si no hay datos adicionales.
4. Tipado débil. No hagas un evento "todo-en-uno" EventHandler y metas ahí de todo. Crea una clase derivada separada para cada evento — la legibilidad y la fiabilidad aumentan.
5. Errores al invocar el evento. Siempre comprueba si hay suscriptores: SomeEvent?.Invoke(this, e). Sin suscriptores la referencia del evento es null.
6. Violación de la encapsulación. No invoques el evento desde fuera de la clase-publicadora. Un evento es solo para suscribirse/dese suscribirse; la invocación debe hacerse internamente a través de un método On....
GO TO FULL VERSION