Control y generación de eventos

Los eventos de .NET se basan en el modelo de delegado. El modelo de delegado sigue el patrón de diseño del observador, que permite que un suscriptor se registre con un proveedor y reciba notificaciones de él. El emisor de un evento inserta una notificación de que se ha producido un evento, y un receptor de eventos recibe la notificación y define una respuesta a la misma. En este artículo se describen los componentes principales del modelo de delegado, cómo consumir eventos en las aplicaciones y cómo implementar eventos en el código.

Events

Un evento es un mensaje que envía un objeto cuando ocurre una acción. La acción podría deberse a la interacción del usuario, como hacer clic en un botón, o podría derivarse de cualquier otra lógica del programa, como el cambio del valor de una propiedad. El objeto que provoca el evento se conoce como emisor del evento. El emisor del evento no sabe qué objeto o método recibirá (controlará) los eventos que genera. El evento normalmente es un miembro del emisor del evento; por ejemplo, el evento Click es un miembro de la clase Button, y el evento PropertyChanged es un miembro de la clase que implementa la interfaz INotifyPropertyChanged.

Para definir un evento, se utiliza la palabra clave event de C# o Event de Visual Basic en la signatura de la clase de eventos y se especifica el tipo de delegado para el evento. Los delegados se describen en la sección siguiente.

Normalmente, para generar un evento, se agrega un método marcado como protected y virtual (en C#) o Protected y Overridable (en Visual Basic). Asigne a este método el nombre OnEventName; por ejemplo, OnDataReceived. El método debe tomar un parámetro que especifica un objeto de datos de evento, que es un objeto de tipo EventArgs o un tipo derivado. Este método se proporciona para permitir que las clases derivadas reemplacen la lógica para generar el evento. Una clase derivada siempre debería llamar al método OnEventName de la clase base para asegurarse de que los delegados registrados reciben el evento.

En el ejemplo siguiente se muestra cómo declarar un evento denominado ThresholdReached. El evento está asociado al delegado EventHandler y se genera en un método denominado OnThresholdReached.

class Counter
{
    public event EventHandler ThresholdReached;

    protected virtual void OnThresholdReached(EventArgs e)
    {
        EventHandler handler = ThresholdReached;
        handler?.Invoke(this, e);
    }

    // provide remaining implementation for the class
}
Public Class Counter
    Public Event ThresholdReached As EventHandler

    Protected Overridable Sub OnThresholdReached(e As EventArgs)
        RaiseEvent ThresholdReached(Me, e)
    End Sub

    ' provide remaining implementation for the class
End Class

Delegados

Un delegado es un tipo que tiene una referencia a un método. Un delegado se declara con una signatura que muestra el tipo de valor devuelto y los parámetros para los métodos a los que hace referencia, y únicamente puede contener referencias a los métodos que coinciden con su signatura. Por lo tanto, un delegado equivale a un puntero a función con seguridad o a una devolución de llamada. Una declaración de delegado es suficiente para definir una clase de delegado.

Los delegados tienen muchos usos en .NET. En el contexto de los eventos, un delegado es un intermediario (o un mecanismo de puntero) entre el origen del evento y el código que lo controla. Para asociar un delegado a un evento se incluye el tipo de delegado en la declaración del evento, como se muestra en el ejemplo de la sección anterior. Para obtener más información sobre los delegados, vea la clase Delegate.

.NET proporciona los delegados EventHandler y EventHandler<TEventArgs> que admiten la mayoría de los escenarios de eventos. Use el delegado EventHandler para todos los eventos que no incluyen datos de evento. Use el delegado EventHandler<TEventArgs> para los eventos que incluyen datos sobre el evento. Estos delegados no tienen ningún valor de tipo devuelto y toman dos parámetros (un objeto para el origen del evento y un objeto para los datos del evento).

Los delegados son de multidifusión, lo que significa que pueden guardar referencias a más de un método de control de eventos. Para obtener información detallada, vea la página de referencia de Delegate. Los delegados permiten realizar un control de eventos más flexible y detallado. Un delegado actúa como remitente de eventos de la clase que genera el evento y mantiene una lista de los controladores registrados para el evento.

Para los escenarios en que no funcionan los delegados EventHandler y EventHandler<TEventArgs>, puede definir un delegado. Los escenarios para los es necesario definir un delegado son poco habituales, como cuando se debe ejecutar código que no reconoce genéricos. Los delegados se marcan con la palabra clave delegate de C# y Delegate de Visual Basic en la declaración. En el ejemplo siguiente se muestra cómo declarar un delegado denominado ThresholdReachedEventHandler.

public delegate void ThresholdReachedEventHandler(object sender, ThresholdReachedEventArgs e);
Public Delegate Sub ThresholdReachedEventHandler(sender As Object, e As ThresholdReachedEventArgs)

Datos de evento

Los datos asociados a un evento se pueden proporcionar a través de una clase de datos de evento. .NET proporciona muchas clases de datos de evento que puede utilizar en las aplicaciones. Por ejemplo, la clase SerialDataReceivedEventArgs es la clase de datos de evento del evento SerialPort.DataReceived. En .NET se sigue un patrón de nombres que consiste en finalizar todas las clases de datos de evento con EventArgs. Para determinar qué clase de datos de evento está asociada a un evento, basta con examinar el delegado del evento. Por ejemplo, el delegado SerialDataReceivedEventHandler incluye entre sus parámetros la clase SerialDataReceivedEventArgs.

La clase EventArgs es el tipo base para todas las clases de datos de evento. EventArgs también es la clase que se usa cuando un evento no tiene datos asociados. Cuando cree un evento que solo sirva para notificar a otras clases que algo ha sucedido y que no necesite pasar ningún dato, incluya la clase EventArgs como segundo parámetro del delegado. Puede pasar el valor EventArgs.Empty cuando no se proporciona ningún dato. El delegado EventHandler incluye la clase EventArgs como parámetro.

Si desea crear una clase de datos de evento personalizada, cree una clase que se derive de EventArgs y, a continuación, especifique los miembros que sean necesarios para pasar los datos relacionados con el evento. Normalmente, debe usar el mismo patrón de asignación de nombres que se usa en .NET y finalizar el nombre de la clase de los datos de evento con EventArgs.

En el ejemplo siguiente se muestra una clase de datos de evento denominada ThresholdReachedEventArgs. Contiene propiedades específicas del evento que se genera.

public class ThresholdReachedEventArgs : EventArgs
{
    public int Threshold { get; set; }
    public DateTime TimeReached { get; set; }
}
Public Class ThresholdReachedEventArgs
    Inherits EventArgs

    Public Property Threshold As Integer
    Public Property TimeReached As DateTime
End Class

Controladores de eventos

Para responder a un evento, se define un método controlador de eventos en el receptor de eventos. Este método debe coincidir con la signatura del delegado del evento que se está controlando. En el controlador de eventos, se realizan las acciones que es necesario llevar a cabo cuando se genera el evento, como recopilar los datos proporcionados por el usuario cuando este hace clic en un botón. Para recibir notificaciones cuando se genera el evento, el método controlador de eventos debe suscribirse al evento.

En el ejemplo siguiente se muestra un método de control de eventos denominado c_ThresholdReached que coincide con la signatura del delegado EventHandler. El método se suscribe al evento ThresholdReached.

class Program
{
    static void Main()
    {
        var c = new Counter();
        c.ThresholdReached += c_ThresholdReached;

        // provide remaining implementation for the class
    }

    static void c_ThresholdReached(object sender, EventArgs e)
    {
        Console.WriteLine("The threshold was reached.");
    }
}
Module Module1

    Sub Main()
        Dim c As New Counter()
        AddHandler c.ThresholdReached, AddressOf c_ThresholdReached

        ' provide remaining implementation for the class
    End Sub

    Sub c_ThresholdReached(sender As Object, e As EventArgs)
        Console.WriteLine("The threshold was reached.")
    End Sub
End Module

Controladores de eventos estáticos y dinámicos

.NET permite a los suscriptores registrarse para las notificaciones de eventos estática o dinámicamente. Los controladores de eventos estáticos son efectivos durante toda la vida de la clase cuyos eventos controlan. Los controladores de eventos dinámicos se activan y desactivan explícitamente durante la ejecución de un programa, normalmente en respuesta a alguna lógica condicional del programa. Por ejemplo, pueden utilizarse si las notificaciones de eventos solo son necesarias en condiciones específicas o si una aplicación proporciona varios controladores de eventos y las condiciones en tiempo de ejecución determinan cuál es el que debe utilizarse. En el ejemplo de la sección anterior se muestra cómo agregar dinámicamente un controlador de eventos. Para obtener más información, vea Eventos (en Visual Basic) y Eventos (en C#).

Generar múltiples eventos

Si la clase genera varios eventos, el compilador genera un campo por cada instancia de delegado de eventos. Si el número de eventos es alto, es posible que el costo de almacenamiento de un campo por delegado no sea aceptable. Para estos casos, .NET dispone de propiedades de evento que se pueden usar con otra estructura de datos (de elección propia) para almacenar los delegados de eventos.

Las propiedades de evento están compuestas de declaraciones de evento acompañadas de descriptores de acceso de evento. Los descriptores de acceso de eventos son métodos que se definen para agregar o quitar instancias de delegados de eventos de la estructura de datos de almacenamiento. Hay que tener en cuenta que las propiedades de evento son más lentas que los campos de evento, ya que se debe recuperar cada delegado de evento antes de poder invocarlo. La memoria y la velocidad se ven afectadas. Si la clase define muchos eventos que no se provocan con frecuencia, es posible que desee implementar propiedades de evento. Para obtener más información, vea Cómo: Controlar varios eventos mediante las propiedades de evento.

Title Descripción
Cómo: Provocar y utilizar eventos Contiene ejemplos de cómo generar y consumir eventos.
Cómo: Controlar varios eventos mediante las propiedades de evento Muestra cómo utilizar propiedades de evento para controlar varios eventos.
Modelo de diseño de observador Describe el patrón de diseño que permite que un suscriptor se registre con un proveedor y reciba notificaciones de dicho proveedor.

Vea también