Manipular e gerar eventos

Os eventos no .NET são baseados no modelo de representante. O modelo de representante segue o padrão de design do observador, que permite a um assinante se registrar em um provedor e receber notificações dele. Um remetente de eventos envia uma notificação por push de que um evento ocorreu e um receptor de eventos recebe essa notificação e define uma resposta. Este artigo descreve os principais componentes do modelo de representante, como consumir eventos em aplicativos e como implementar eventos no código.

Eventos

Um evento é uma mensagem enviada por um objeto para sinalizar a ocorrência de uma ação. A ação pode ser causada pela interação do usuário, como o clique em um botão, ou ser resultado de alguma outra lógica de programa, como a alteração do valor de uma propriedade. O objeto que aciona o evento é chamado de remetente do evento. O remetente do evento não sabe qual objeto ou método receberá (identificador) os eventos que ele aciona. O evento normalmente é membro do remetente do evento. Por exemplo, o evento Click é membro da classe Button e o evento PropertyChanged é membro da classe que implementa a interface INotifyPropertyChanged.

Para definir um evento, use a palavra-chave event no C# ou Event no Visual Basic na assinatura da sua classe de evento e especifique o tipo de representante para o evento. Os representantes são descritos na próxima seção.

Normalmente, para acionar um evento, você adiciona um método que é marcado como protected e virtual (em C#) ou Protected e Overridable (no Visual Basic). Dê a esse método o nome OnEventName; por exemplo, OnDataReceived. O método deve usar um parâmetro que especifica um objeto de dados de evento, que é um objeto do tipo EventArgs ou um tipo derivado. Você fornece esse método para permitir que as classes derivadas substituam a lógica para acionamento do evento. Uma classe derivada sempre deve chamar o método OnEventName da classe base a fim de garantir que os representantes registrados recebam o evento.

O exemplo de código a seguir mostra como declarar um evento denominado ThresholdReached. O evento está associado ao representante EventHandler e é gerado em um método chamado 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

Um representante é um tipo que contém uma referência a um método. Um representante é declarado com uma assinatura que mostra o tipo de retorno e os parâmetros para os métodos aos quais faz referência, e pode conter referências apenas aos métodos que correspondem à sua assinatura. Portanto, um representante é equivalente a um ponteiro de função fortemente tipado ou um retorno de chamada. Uma declaração de representante é suficiente para definir uma classe de representante.

Representantes têm muitos usos no .NET. No contexto de eventos, um representante é um intermediário (ou mecanismo do tipo ponteiro) entre a origem do evento e o código que manipula o evento. Associe um representante a um evento incluindo o tipo de representante na declaração do evento, como mostrado no exemplo da seção anterior. Para obter mais informações sobre representantes, consulte a classe Delegate.

O .NET fornece os representantes EventHandler e EventHandler<TEventArgs> para dar suporte à maioria dos cenários de evento. Use o representante EventHandler para todos os eventos que não incluem dados de evento. Use o representante EventHandler<TEventArgs> para eventos que incluem dados sobre o evento. Esses representantes não têm valor de tipo de retorno e usam dois parâmetros (um objeto para a origem do evento e um objeto para dados do evento).

Os representantes são multicast, o que significa que eles podem manter referências a mais de um método de manipulação de eventos. Para obter detalhes, consulte a página de referência Delegate. Os representantes proporcionam flexibilidade e controle refinado na manipulação de eventos. Um representante atua como um dispatcher de evento para a classe que aciona o evento ao manter uma lista de manipuladores de eventos registrados para o evento.

Para cenários em que os representantes EventHandler e EventHandler<TEventArgs> não funcionam, você pode definir um representante. Os cenários que exigem que você defina um representante são muito raros; por exemplo, quando você deve trabalhar com código que não reconhece genéricos. Marque um representante com a palavra-chave delegate no C# e Delegate no Visual Basic na declaração. O exemplo a seguir mostra como declarar um representante chamado ThresholdReachedEventHandler.

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

Dados de evento

Os dados associados a um evento podem ser fornecidos por meio de uma classe de dados do evento. O .NET fornece muitas classes de dados de evento que podem ser usadas em seus aplicativos. Por exemplo, a classe SerialDataReceivedEventArgs é a classe de dados de evento do evento SerialPort.DataReceived. O .NET segue um padrão de nomenclatura de terminação para todas as classes de dados de evento com EventArgs. Determine qual classe de dados de evento está associada a um evento observando o representante do evento. Por exemplo, o representante SerialDataReceivedEventHandler inclui a classe SerialDataReceivedEventArgs como um de seus parâmetros.

A classe EventArgs é o tipo base para todas as classes de dados de evento. EventArgs também é a classe usada quando um evento não tem nenhum dado associado. Quando você criar um evento cuja finalidade seja apenas notificar outras classes de que algo aconteceu e que não precise passar nenhum dado, inclua a classe EventArgs como o segundo parâmetro no representante. Você poderá passar o valor EventArgs.Empty quando nenhum dado for fornecido. O representante EventHandler inclui a classe EventArgs como um parâmetro.

Quando quiser criar uma classe de dados de evento personalizada, crie uma classe derivada de EventArgs e forneça todos os membros necessários para passar dados que estejam relacionados ao evento. Normalmente, você deve usar o mesmo padrão de nomenclatura do .NET e terminar o nome da classe de dados de evento com EventArgs.

O exemplo a seguir mostra uma classe de dados de evento chamada ThresholdReachedEventArgs. Ele contém propriedades que são específicas ao evento que está sendo acionado.

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

Manipuladores de eventos

Para responder a um evento, você pode definir um método de manipulador de eventos no receptor do evento. Esse método deve corresponder à assinatura do representante para o evento que está sendo manipulado. No manipulador de eventos, execute as ações que são necessárias quando o evento é acionado, como coletar a entrada do usuário depois que ele clica em um botão. Para receber notificações de ocorrência de eventos, o método de manipulador de eventos deve estar inscrito no evento.

O exemplo a seguir mostra um método de manipulador de eventos chamado c_ThresholdReached que corresponde à assinatura para o representante EventHandler. O método está inscrito no 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

Manipuladores de eventos estáticos e dinâmicos

O .NET permite que os assinantes se registrem para receber notificações de eventos de modo estático ou dinâmico. Os manipuladores de eventos estáticos permanecem em vigor por toda a vida da classe cujos eventos eles manipulam. Os manipuladores de eventos dinâmicos são ativados e desativados explicitamente durante a execução do programa, geralmente em resposta a alguma lógica de programa condicional. Por exemplo, eles podem ser usados se as notificações de eventos forem necessárias apenas sob determinadas condições, ou se um aplicativo fornecer vários manipuladores de eventos e as condições de tempo de execução definirem o apropriado para uso. O exemplo na seção anterior mostra como adicionar dinamicamente um manipulador de eventos. Para obter mais informações, veja Eventos (no Visual Basic) e Eventos (em C#).

Acionando vários eventos

Se sua classe acionar vários eventos, o compilador vai gerar um campo por instância de representante de evento. Se o número de eventos for grande, o custo de armazenamento de um campo por representante pode não ser aceitável. Para esses casos, o .NET fornece propriedades de evento que você pode usar com outra estrutura de dados de sua escolha para armazenar representantes de eventos.

As propriedades de evento consistem em declarações de evento acompanhadas por acessadores de evento. Os acessadores de evento são métodos que você define para adicionar ou remover instâncias de representante de evento da estrutura de dados de armazenamento. Observe que as propriedades de evento são mais lentas que os campos de evento, pois cada representante de evento deve ser recuperado para que possa ser invocado. A compensação está entre a memória e a velocidade. Se sua classe define muitos eventos que raramente são acionados, você desejará implementar as propriedades de evento. Para saber mais, confira Como manipular vários eventos usando propriedades de evento.

Título Descrição
Como acionar e consumir eventos Contém exemplos de como acionar e consumir eventos.
Como manipular vários eventos usando propriedades de evento Mostrar como usar propriedades de evento para manipular vários eventos.
Padrão de design do observador Descreve o padrão de design que permite a um assinante se registrar em um provedor e receber notificações dele.

Confira também