1. Estilo de declaración y nombrado de eventos
Los eventos no son solo delegados. Son una entidad separada para la comunicación entre partes de la aplicación, y su declaración debe ser clara.
Usa el tipo de delegado correcto
En el 99% de los casos usa los delegados estándar:
- EventHandler — para eventos sin datos.
- EventHandler<TEventArgs> — cuando hace falta pasar parámetros.
La estandarización facilita el mantenimiento del código e integración con librerías de .NET. No inventes tu propio delegado si sirve EventHandler.
public event EventHandler SomethingHappened; // Sin datos
public event EventHandler<MyEventArgs> DataReceived; // Hay datos adicionales
Si necesitas personalización especial — declara un delegado propio, pero es raro.
Nombrado de eventos
En .NET los eventos se nombran en pasado: Completed, Clicked, Changed, Received. Esto enfatiza que el hecho ya ocurrió.
Ejemplos:
public event EventHandler DataLoaded; // Los datos fueron cargados
public event EventHandler<MessageEventArgs> MessageReceived; // Mensaje recibido
public event EventHandler Saving; // Se inició el proceso de guardado
A veces se usa la forma Changing para eventos "antes" del cambio, para dar oportunidad de intervenir.
2. Organización de la clase publicadora: método virtual OnEvent
Siempre añade un método protected virtual que invoque el evento: punto central de invocación, extensibilidad vía herencia y comportamiento predecible.
public class FileLoader
{
public event EventHandler<FileLoadedEventArgs> FileLoaded;
protected virtual void OnFileLoaded(FileLoadedEventArgs e)
{
FileLoaded?.Invoke(this, e);
}
public void Load(string filename)
{
// ... lógica de carga de archivo ...
OnFileLoaded(new FileLoadedEventArgs(filename));
}
}
public class FileLoadedEventArgs : EventArgs
{
public string FileName { get; }
public FileLoadedEventArgs(string fileName) => FileName = fileName;
}
Que solo OnFileLoaded invoque el evento — así es más fácil mantenerlo y testearlo.
3. Reglas de suscripción y desuscripción: ciclo de vida, IDisposable
Si la vida del suscriptor es menor que la del publicador, debes desuscribirte antes de destruir el suscriptor. Es conveniente implementar IDisposable y desuscribirse en Dispose().
public class TemporaryListener : IDisposable
{
private readonly Publisher _publisher;
public TemporaryListener(Publisher publisher)
{
_publisher = publisher;
_publisher.DataReceived += HandleData;
}
private void HandleData(object sender, EventArgs e)
{
// Trabajo con los datos
}
public void Dispose()
{
_publisher.DataReceived -= HandleData;
}
}
// Uso con using:
using (var listener = new TemporaryListener(myPublisher))
{
// listener escucha eventos aquí
}
// Al salir del using - Dispose es llamado, la dessuscripción ocurrió
Si olvidas desuscribirte, el publicador mantendrá la referencia al delegado del suscriptor — tendrás fugas de memoria y "objetos zombie".
4. Invocación segura frente a hilos
En código multihilo los suscriptores pueden añadirse/quitándose en paralelo con la invocación del evento. Esto puede causar race conditions y NullReferenceException. Usa el patrón seguro: copia el delegado a una variable local.
protected virtual void OnSomethingHappened()
{
EventHandler handler = SomethingHappened;
handler?.Invoke(this, EventArgs.Empty);
}
Con C# 6+ es suficiente:
SomethingHappened?.Invoke(this, EventArgs.Empty);
5. Usa EventArgs en vez de object
No pases datos vía object ni campos de la clase. Usa tipado estricto mediante subclases de EventArgs.
public class DownloadCompletedEventArgs : EventArgs
{
public string FileName { get; }
public long Size { get; }
public DownloadCompletedEventArgs(string fileName, long size)
{
FileName = fileName;
Size = size;
}
}
public event EventHandler<DownloadCompletedEventArgs> DownloadCompleted;
6. Documenta eventos y suscriptores
Documenta: cuándo se invoca el evento, el significado de los campos de EventArgs, si hace falta desuscribirse y cuándo.
/// <summary>
/// El evento ocurre después de una carga exitosa de datos.
/// </summary>
public event EventHandler<DataLoadedEventArgs> DataLoaded;
7. Recomendaciones resumidas sobre la arquitectura de eventos
Separa responsabilidades
El publicador solo notifica del hecho. El suscriptor decide cuándo suscribirse y desuscribirse.
Evita "bombardear" con eventos
No generes el mismo evento decenas de veces por segundo sin necesidad — es carga innecesaria.
No uses eventos para comunicación bidireccional
Los eventos son para el esquema "uno notifica — muchos escuchan". Para comunicación bidireccional considera interfaces, callbacks u otros mecanismos.
No almacenes referencias explícitas a suscriptores en la clase
No mantengas referencias explícitas a los suscriptores — los eventos y delegados se encargan de eso automáticamente.
8. Antipatrons clásicos
Eventos sin tipar
public event Action<object> SomethingHappened; // No está claro qué hay dentro
Malo: se rompe el tipado, hacen falta casts, se pierde mantenibilidad.
Olvidar la desuscripción
public class ShortLivedListener
{
public ShortLivedListener(Publisher p) =>
p.DataReceived += DoWork;
private void DoWork(object sender, EventArgs e) { /* ... */ }
// No hay Dispose, no hay desuscripción => objetos zombie!
}
Violación de SRP
Clase que es a la vez publicador, suscriptor y handler — mezcla de responsabilidades. Separa responsabilidades.
9. Uso práctico en entrevistas y proyectos
En muchos proyectos con publicación-suscripción, una organización correcta de eventos es clave para escalabilidad y mantenimiento. En entrevistas suelen pedir:
- implementar un sistema de eventos con tipado correcto,
- mostrar gestión del ciclo de vida de los suscriptores,
- explicar la invocación segura frente a hilos.
Código de eventos limpio, documentado y bien organizado te destacará entre los candidatos.
GO TO FULL VERSION