CodeGym /Cursos /C# SELF /Plantilla estándar de eventos (

Plantilla estándar de eventos ( EventHandler/ EventArgs)

C# SELF
Nivel 52, Lección 3
Disponible

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
public event Action<int> MyEvent;
No Baja No
public delegate void MyHandler(object, int); event MyHandler ...
Media No (raro)
public event EventHandler;
No (EventArgs) Alta Sí, estándar
public event EventHandler<MyArgs>;
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....

2
Tarea
C# SELF, nivel 52, lección 3
Bloqueada
Creación de un evento con EventHandler y EventArgs personalizado
Creación de un evento con EventHandler y EventArgs personalizado
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION