Un evento en C# es un mecanismo que permite a una clase o un objeto notificar a otras clases u objetos que algo ha ocurrido.
Cuando ocurre un evento, se llama a los métodos asociados permitiendo que las clases reaccionen a las acciones desencadenadas.
Los eventos se basan en los delegados, que son tipos que encapsulan referencias a métodos. Sin embargo los eventos tienen pequeñas diferencias respecto a los delegados. Una de ellas es que solo la clase que dispone del evento puede invocarlo.
Sintaxis de eventos
Para definir un evento, primero tenemos que definir un tipo de delegado que especifique la firma del método que queremos que respondan al evento. A esta función le llamamos handler
.
Por otro lado, tenemos que declarar el evento en sí. Este utiliza tipo de delegado que hemos definido anteriormente, antecediendo la palabra reservada event
.
Parece un poco complicado y rígido, pero tiene su sentido 😉. Más abajo veremos que hay una forma más sencilla de hacerlo. Pero de momento está bien para entender la base.
Veámoslo con un ejemplo,
// definicion de la función que gestionará el evento
public delegate void MiEventoHandler(string mensaje);
public class Publicador
{
// definición del evento
public event MiEventoHandler MiEvento;
public void Notificar(string mensaje)
{
MiEvento?.Invoke(mensaje);
}
}
En este ejemplo,
- MiEventoHandler es el tipo de función que queremos que “responda” a nuestro evento. En este caso son métodos que toman un
string
como parámetro y no retornan valor - MiEvento es un evento en sí. Se define usando el delegado anterior, antecediendo la palabra
event
para indicar que es un evento
Finalmente, cuando haya ocurrido “lo que sea que genere el evento”, y queramos dispararlo, hacemos
MiEvento?.Invoke(mensaje)
Cómo consumir los eventos
Ya tenemos sabemos cómo definir y lanzar Eventos. Ahora vamos a ver como otras partes del programa pueden suscribirse a estos eventos para ser informados de que ha pasado algo.
Suscripción a eventos
Para suscribirse a un evento se utiliza el operador +=
, agregando un método manejador de eventos que coincida con la firma del delegado del evento.
public static void ImprimirMensaje(string mensaje)
{
Console.WriteLine("Evento recibido: " + mensaje);
}
// creamos un objeto publicador
var publicador = new Publicador();
// suscribimos ImprimirMensaje al evento
publicador.MiEvento += ImprimirMensaje;
// forzamos lanzar el evento
publicador.Notificar("Hola, evento!");
// Output:
// Evento recibido: Hola, evento!
En este ejemplo, suscribimos el método ImprimirMensaje
en la clase Suscriptor
al evento MiEvento
del Publicador
.
En un proyecto real el objeto Publicador
estaría haciendo sus cosas. Cuando quisiera informar de que lanza
Desuscripción de eventos
Para desuscribirse de un evento, se utiliza el operador -=
, eliminando el método manejador de eventos del evento.
publicador.MiEvento -= suscriptor.ManejarEvento;
Desuscribirse de eventos es importante para evitar problemas de memoria y referencias colgantes, especialmente en aplicaciones de larga duración.
Uso de eventos genéricos
La sintaxis normal para la creación de Eventos en C# es un poco “verbose”, porque tenemos que definir un delegado con la firma del método que gestionará el evento.
Para simplificar la sintaxis se introdujo el delegado genérico EventHandler<TEventArgs>
en la versión 2.0 de .NET Framework.
Es la sintaxis que usaréis normalmente
Este delegado proporciona una forma genérica de manejar eventos, lo que significa que puede manejar cualquier tipo de argumentos de eventos sin tener que definir un delegado personalizado para cada tipo de evento.
public class Publicador
{
// definición del evento
public event EventHandler<string> MiEvento;
public void Notificar(string mensaje)
{
MiEvento?.Invoke(mensaje);
}
}
EventHandler<TEventArgs>
es una abstracción útil que encapsula un método que toma dos parámetros:
- El objeto que desencadenó el evento (
sender
) - Un objeto que contiene datos relacionados con el evento (
TEventArgs
). Usualmente,TEventArgs
es una clase que hereda deEventArgs
y puede contener información adicional sobre el evento.
Por tanto, las funciones que se suscriben al Evento tendrían la siguiente forma,
publicador.MiEvento += (s, e) => ImprimirMensaje;
Que es la forma más habitual de EventHandler que encontraréis en C#.
Ejemplos prácticos
Uso de eventos con un botón
Aunque los eventos no son exclusivos del Interface de Usuario, es uno de los usos principales que tienen los eventos.
Veamos cómo podríamos suscribir una función al Evento Click de un botón del Interface de Usuario.
Boton boton = new Boton();
boton.Click += (sender, args) => Console.WriteLine("Evento Click ha ocurrido.");
Creación de una alarma
Este ejemplo muestra cómo usar Eventos fuera del ámbito del interface de usuario. Por ejemplo, creando una alarma que dispara un evento cuando se activa.
public class Alarma
{
public event EventHandler AlarmaActivada; // Define un evento AlarmaActivada
public void Activar()
{
AlarmaActivada?.Invoke(this, EventArgs.Empty); // Dispara el evento AlarmaActivada
}
}
Alarma alarma = new Alarma();
// Suscribirse al evento AlarmaActivada
alarma.AlarmaActivada += (sender, args) => Console.WriteLine("¡Alarma activada!");
// Activar la alarma
alarma.Activar();
Patrón observador con intercambio de mensajes
Este ejemplo implementa un patrón observador muy sencillo, donde un emisor envía mensajes a un receptor.
public class Emisor
{
public event EventHandler<string> MensajeEnviado; // Define un evento MensajeEnviado
public void EnviarMensaje(string mensaje)
{
Console.WriteLine($"Mensaje enviado: {mensaje}");
MensajeEnviado?.Invoke(this, mensaje); // Dispara el evento MensajeEnviado
}
}
public class Receptor
{
public Receptor(Emisor emisor)
{
// Suscribirse al evento MensajeEnviado del emisor
emisor.MensajeEnviado += (sender, mensaje) => Console.WriteLine($"Mensaje recibido: {mensaje}");
}
}
Emisor emisor = new Emisor();
Receptor receptor = new Receptor(emisor);
emisor.EnviarMensaje("Hola, receptor!");