CodeGym /Cursos /C# SELF /Template padrão de eventos (

Template padrão de eventos ( EventHandler/ EventArgs)

C# SELF
Nível 52 , Lição 3
Disponível

1. Introdução

Vamos relembrar: evento é um contrato público, a promessa do seu código de "chamar" os assinantes quando algo importante acontecer. Nos exemplos das aulas anteriores você pode ter visto uma declaração assim:


public event Action<string> MessageSent;

Ou até:


public delegate void MyHandler(int value);
public event MyHandler SomethingHappened;

Isso funciona, mas esse jeito traz uma porção de problemas — desde discrepâncias na assinatura dos métodos até a impossibilidade de saber quem disparou o evento e o que exatamente aconteceu. Imagine se ao apertar uma tecla no teclado o botão disparasse aleatoriamente, cada vez de um jeito diferente dentro do mesmo programa para a mesma ação. Por exemplo, você joga e aperta espaço. Um instante atrás isso era pular, agora de repente abre o inventário. Um pesadelo, não um padrão!

No .NET existe algo parecido com uma Constituição para eventos — é um acordo sobre a assinatura do handler e a estrutura da informação passada com o evento. O estilo canônico é assim:


void Handler(object sender, EventArgs args);

Soa familiar? Essa linha aparece em todo lugar: desde cliques em WinForms até eventos do sistema em ASP.NET e mesmo em bibliotecas terceiras.

2. O que são EventHandler e EventArgs

A ideia principal: cada evento comunica duas coisas:

  • Quem disparou o evento? sender
  • O que aconteceu? EventArgs — dados adicionais

No mundo C# isso se apresenta assim:


public delegate void EventHandler(object sender, EventArgs e);
  • object sender — referência ao iniciador do evento. Pode ser qualquer coisa — geralmente this.
  • EventArgs e — objeto com informação extra sobre o evento. Para cenários simples usa-se EventArgs.Empty, para casos mais complexos — seus próprios derivados de EventArgs.

Fato curioso

Nas recomendações oficiais do .NET para APIs públicas é comum que um evento tenha a assinatura (object sender, EventArgs e). Se você encontrar um evento sem sender e EventArgs — provavelmente é uma simplificação do autor, não o estilo canônico do .NET.

Grande vantagem de um único padrão

Com um padrão único fica mais fácil logar, testar, assinar de forma genérica e estender o sistema. Abra o código-fonte do WinForms, WPF, ASP.NET — você verá o mesmo padrão.

3. Como usar o template padrão de eventos

1. Usando o delegate embutido EventHandler

Em vez de declarar seu próprio delegate você pode usar o pronto:


public event EventHandler SmthHappened;

E agora o handler sempre tem a mesma aparência:


private void OnSmthHappened(object sender, EventArgs e)
{
    // Lógica de reação ao evento
}

A inscrição continua simples:


myObj.SmthHappened += OnSmthHappened;

Importante: se o evento não transmite dados adicionais, use EventArgs.Empty.

2. Criando seus próprios argumentos de evento

Se precisar passar informação (resultado de cálculo, nome de arquivo, erro), crie um derivado de EventArgs:


public class CalculationEventArgs : EventArgs
{
    public double Result { get; }
    public CalculationEventArgs(double result) => Result = result;
}

Depois use o delegate genérico pronto — EventHandler<TEventArgs>:


public event EventHandler<CalculationEventArgs> CalculationFinished;

E o handler agora recebe exatamente o seu tipo de argumentos:


private void OnCalculationFinished(object sender, CalculationEventArgs e)
{
    Console.WriteLine($"Cálculo concluído. Resultado: {e.Result}");
}

4. Exemplo de aplicação

Vamos evoluir nosso projeto didático — imagine que temos uma calculadora que soma ou subtrai números e notifica a conclusão da operação via evento.

Exemplo minimalista


public class Calculator
{
    public event EventHandler<CalculationEventArgs> CalculationPerformed;

    public void Add(int a, int b)
    {
        int result = a + b;
        // "Disparamos" o evento
        CalculationPerformed?.Invoke(this, new CalculationEventArgs(result));
    }
}

public class CalculationEventArgs : EventArgs
{
    public int Result { get; }
    public CalculationEventArgs(int result) => Result = result;
}

Nosso subscribe e uso:


var calc = new Calculator();
calc.CalculationPerformed += (sender, e) =>
{
    Console.WriteLine($"Resultado da operação: {e.Result}");
};
calc.Add(10, 20);
// Vai imprimir: Resultado da operação: 30

Esse é todo o "padrão": o handler sempre recebe o objeto-autor e o objeto de argumentos — universal e claro.

5. Pontos úteis

Encapsular a chamada do evento: bom estilo

No .NET é comum colocar a lógica de disparo do evento em um método protegido com prefixo On:


protected virtual void OnCalculationPerformed(CalculationEventArgs e)
{
    CalculationPerformed?.Invoke(this, e);
}

E dentro da lógica ("Add", "Subtract" etc.) você só chama esse método:


public void Add(int a, int b) => OnCalculationPerformed(new CalculationEventArgs(a + b));

Esse estilo permite que classes derivadas sobrescrevam o comportamento do evento e reduz a chance de esquecer de dispará-lo.

Esquema: Como é um evento no padrão .NET

graph LR
    A[Objeto-publicador] -- "event EventHandler/ EventHandler<TEventArgs>" --> B[Lista de assinantes]
    B -- "Método-handler (object sender, EventArgs e)" --> C[Reação ao evento]
    A -- "this (sender)" --> C
    A -- "EventArgs (dados)" --> C

Comparação dos modos de declarar eventos

Abordagem Transmite o iniciador (sender) Transmite argumentos Universalidade Uso no .NET
public event Action<int> MyEvent;
Não Sim Baixa Não
public delegate void MyHandler(object, int); event MyHandler ...
Sim Sim Média Não (raro)
public event EventHandler;
Sim Não (EventArgs) Alta Sim, padrão
public event EventHandler<MyArgs>;
Sim Sim (MyArgs) Altíssima Sim, padrão

Utilidade prática em entrevistas e código de produção

Usar o template padrão de eventos é um must-have para um desenvolvedor .NET. Em entrevistas provavelmente vão perguntar sobre EventHandler e o par sender/EventArgs. Evento do tipo Action<T> costuma ser visto como "simplificação".

Em projetos reais esse padrão facilita trabalhar em equipe, testar, estender e manter o código. Bibliotecas externas (logging, profilers) integram-se mais facilmente quando o formato padrão é usado.

Fluxograma de chamada de evento

flowchart TD
    subgraph A[Classe-publicadora]
        C1((Objeto))
        C2[Método que chama o evento]
        C3["event EventHandler<MyArgs>"]
    end
    subgraph B[Classe-assinante]
        D1((Assinatura))
        D2["Handler do evento (object sender, MyArgs args)"]
    end
    C1 -- Chama --> C2
    C2 -- "Invoke(this, args)" --> C3
    C3 -- "Notifica" --> D1
    D1 -- "Executa" --> D2

6. Dicas, nuances e erros típicos

1. Assinatura do handler. Quer fazer o evento do tipo Action<int>? É tentador, mas você perde o sender e compatibilidade com o ecossistema.

2. Passar argumentos. Não confunda EventArgs com parâmetros soltos. Coloque todos os dados do handler no objeto de argumentos.

3. Usar null para argumentos. Em vez de null use EventArgs.Empty quando não houver dados adicionais.

4. Tipagem fraca. Não faça um evento "cura-tudo" do tipo EventHandler e coloque tudo lá. Crie uma classe derivada para cada evento — legibilidade e robustez melhoram.

5. Erros ao chamar um evento. Sempre cheque por assinantes: SomeEvent?.Invoke(this, e). Sem assinantes a referência do evento é null.

6. Quebrar encapsulamento. Não dispare o evento de fora da classe-publicadora. Evento é só para assinatura/cancelamento; o disparo deve ser interno via método On....

Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION