1. Introdução
Então, quando falamos de "evento" (event) no contexto do C#, nos referimos a um mecanismo que permite notificar de forma segura um ou mais objetos que algo aconteceu. Eventos não são mágica: por baixo dos panos são delegates (a palavra-chave delegate) com proteção extra contra uso incorreto.
Imagine assinar um canal do YouTube. O canal (o objeto-dono do evento) tem um botão "Inscrever-se" — qualquer espectador (outro objeto) pode clicar e entrar na lista de assinantes (delegates-handlers). Quando o autor publica um novo vídeo (dispara o evento), só os assinantes recebem a notificação, e só o dono do canal decide quando isso vai acontecer. Detalhe importante: espectadores podem se inscrever ou cancelar a inscrição, mas não podem enviar a notificação por conta própria — esse direito é só do dono do canal.
Um pouco de sintaxe formal
// Declaração do delegate, que vai definir a "forma" do handler do evento
public delegate void SimpleEventHandler();
// Declaração do evento baseado no delegate
public event SimpleEventHandler SomethingHappened;
É simples: event é a palavra-chave para declarar um evento, e o tipo do evento é sempre definido por um delegate.
2. Primeiro exemplo simples: evento "Botão foi clicado!"
Passo 1. Definimos o delegate para os handlers do evento
// Handler do evento: não retorna nada, não recebe parâmetros
public delegate void ButtonClickHandler();
Passo 2. Classe com o evento
public class Button
{
// Evento: é possível assinar e cancelar, mas não é possível dispará-lo de fora
public event ButtonClickHandler Click;
// Método que "aperta o botão"
public void Press()
{
Console.WriteLine("Botão pressionado!");
// Chamada do evento: notificar todos os assinantes
Click?.Invoke();
}
}
Note: o evento é declarado com base no delegate, e no método Press o evento é chamado via ?.Invoke(). Por quê? Porque o evento pode estar vazio (ninguém assinou), então Click é null. O operador de chamada segura garante que os handlers sejam chamados só se houver assinantes.
Passo 3. Assinar o evento e rodar o código
// Exemplo de uso
public class Program
{
public static void Main()
{
var button = new Button();
// Adicionamos um "listener" (assinamos o evento)
button.Click += OnButtonClicked;
button.Click += () => Console.WriteLine("Mais um handler!");
button.Press(); // Simula apertar o botão
// Dá pra cancelar a inscrição do evento
button.Click -= OnButtonClicked;
button.Press();
}
// Handler normal do evento
public static void OnButtonClicked()
{
Console.WriteLine("Handler: O botão foi pressionado!");
}
}
Na primeira pressão ambos os handlers serão executados; na segunda — só a lambda.
3. Diferenças entre evento e delegate
A um delegate você pode atribuir livremente valores e até substituir completamente a lista de assinantes — alguém poderia escrever algo como button.Click = null e todas as assinaturas anteriores "sumiriam".
Com evento é diferente — ele é mais estrito em relação ao objeto. Só o dono da classe onde o evento é declarado pode dispará-lo diretamente (por exemplo, chamar Click() de dentro da classe). Qualquer outro código só pode assinar com += ou cancelar com -=, mas não pode "zerar" tudo de uma vez. Isso protege a encapsulação e evita "quebrar" o sistema de assinaturas.
4. Assinatura: tipos de delegates para eventos, parâmetros
A tradição no .NET é que um handler de evento recebe dois parâmetros — object sender (quem disparou o evento) e argumentos do evento (EventArgs). Recomenda-se usar os delegates EventHandler ou EventHandler<TEventArgs>.
Exemplo: delegate com parâmetros
public delegate void ButtonClickHandler(object sender, EventArgs e);
EventArgs é a classe base para passar informação adicional. Se precisar de mais dados, criamos uma subclasse.
Aplicando isso ao nosso botão
// Classe de argumentos do evento
public class ButtonClickEventArgs : EventArgs
{
public string UserName { get; }
public ButtonClickEventArgs(string userName)
{
UserName = userName;
}
}
public class Button
{
public event EventHandler<ButtonClickEventArgs> Click;
public void Press(string userName)
{
Console.WriteLine("Botão pressionado!");
Click?.Invoke(this, new ButtonClickEventArgs(userName));
}
}
public static void Main()
{
var button = new Button();
button.Click += OnButtonClicked;
button.Press("Vasiliy");
}
public static void OnButtonClicked(object sender, ButtonClickEventArgs e)
{
Console.WriteLine($"O usuário {e.UserName} apertou o botão!");
}
5. Esquema visual: como o evento funciona
+-------------+
| Usuário |
+-------------+
|
v
+--------------+
| Button.Press|
+--------------+
|
v
+-----------------+ +------------------------+
| Chamada Click? |----->---| Assinante 1 |
+-----------------+ +------------------------+
| | Executar handler |
v +------------------------+
+-----------------+ +------------------------+
| Se houver |----->---| Assinante 2 |
| assinante | +------------------------+
: | Executar handler |
v +------------------------+
— Button.Press dispara o evento; os handlers dos assinantes são executados em sequência.
6. Dicas úteis
Forma recomendada de eventos no .NET
Use EventHandler e EventHandler<TEventArgs> para que o código seja compatível com bibliotecas e ferramentas. Essa abordagem facilita a evolução dos eventos: você adiciona novas propriedades aos seus EventArgs sem quebrar os assinantes.
Documentação: EventHandler, event.
Eventos vs delegates
Se você precisa ligar um componente a um método específico — um delegate é suficiente (delegate). Se diferentes partes do programa precisam poder assinar e cancelar a assinatura a qualquer momento — use um evento (event).
7. Erros comuns e situações incômodas ao criar eventos
Erro №1: chamar o evento sem checar null. Se não houver assinantes, tentar chamar o evento diretamente causará NullReferenceException. Use chamada segura:
Click?.Invoke(...);
Erro №2: tentar disparar o evento de fora da classe. Um evento só pode ser elevado (disparado) dentro da classe onde foi declarado. De outra classe o compilador dará erro — isso protege a encapsulação.
Erro №3: múltiplas inscrições do mesmo handler. Se um handler for assinado várias vezes, ele será chamado tantas vezes quanto foi assinado. Essa é uma característica do mecanismo de subscription. Fique de olho em duplicações de += e certifique-se de usar -= corretamente.
GO TO FULL VERSION