csharp-que-son-eventos

Qué son y cómo usar Eventos en C#

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 la 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 de EventArgs 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!");