1. Estilo de declaração e nomeação de eventos
Eventos — não são apenas delegates. São uma entidade separada para comunicação entre partes da aplicação, e a sua declaração deve ser clara.
Use o tipo de delegate correto
Em 99% dos casos use os delegates padrão:
- EventHandler — para eventos sem dados.
- EventHandler<TEventArgs> — quando precisa passar parâmetros.
A padronização facilita a manutenção do código e a integração com bibliotecas .NET. Não invente um delegate novo se EventHandler serve.
public event EventHandler SomethingHappened; // Sem dados
public event EventHandler<MyEventArgs> DataReceived; // Com dados adicionais
Se precisar de personalização especial — declare seu próprio delegate, mas isso é raro.
Nomeação de eventos
No .NET eventos são nomeados no tempo passado: Completed, Clicked, Changed, Received. Isso enfatiza que algo já aconteceu.
Exemplos:
public event EventHandler DataLoaded; // Dados foram carregados
public event EventHandler<MessageEventArgs> MessageReceived; // Mensagem recebida
public event EventHandler Saving; // O processo de salvar começou
Às vezes usa-se a forma Changing para eventos "antes" da mudança, dando chance de intervir.
2. Organização da classe publicadora: método virtual OnEvent
Sempre adicione um método protected virtual que invoque o evento: ponto central de chamada, extensibilidade por herança e comportamento previsível.
public class FileLoader
{
public event EventHandler<FileLoadedEventArgs> FileLoaded;
protected virtual void OnFileLoaded(FileLoadedEventArgs e)
{
FileLoaded?.Invoke(this, e);
}
public void Load(string filename)
{
// ... lógica de carregamento de arquivo ...
OnFileLoaded(new FileLoadedEventArgs(filename));
}
}
public class FileLoadedEventArgs : EventArgs
{
public string FileName { get; }
public FileLoadedEventArgs(string fileName) => FileName = fileName;
}
Deixe apenas OnFileLoaded chamar o evento — assim fica mais fácil manter e testar.
3. Regras de assinatura e descadastro: ciclo de vida, IDisposable
Se a vida do assinante for menor que a do publicador, obrigatoriamente desinscreva antes de destruir o assinante. É conveniente implementar IDisposable e desinscrever em Dispose().
public class TemporaryListener : IDisposable
{
private readonly Publisher _publisher;
public TemporaryListener(Publisher publisher)
{
_publisher = publisher;
_publisher.DataReceived += HandleData;
}
private void HandleData(object sender, EventArgs e)
{
// Trabalho com os dados
}
public void Dispose()
{
_publisher.DataReceived -= HandleData;
}
}
// Uso com using:
using (var listener = new TemporaryListener(myPublisher))
{
// listener escuta eventos aqui
}
// Depois do using - Dispose é chamado, a desinscrição ocorreu
Se esquecer de desinscrever, o publicador manterá a referência ao delegate do assinante — você terá vazamento de memória e "objetos zumbi".
4. Chamada thread-safe de eventos
Em código multithread assinantes podem ser adicionados/removidos paralelamente à invocação do evento. Isso causa race conditions e NullReferenceException. Use o padrão thread-safe: copie o delegate para uma variável local.
protected virtual void OnSomethingHappened()
{
EventHandler handler = SomethingHappened;
handler?.Invoke(this, EventArgs.Empty);
}
Com C# 6+ é suficiente:
SomethingHappened?.Invoke(this, EventArgs.Empty);
5. Use EventArgs em vez de object
Não passe dados via object ou campos da classe. Use tipagem forte através de subclasses de EventArgs.
public class DownloadCompletedEventArgs : EventArgs
{
public string FileName { get; }
public long Size { get; }
public DownloadCompletedEventArgs(string fileName, long size)
{
FileName = fileName;
Size = size;
}
}
public event EventHandler<DownloadCompletedEventArgs> DownloadCompleted;
6. Documentação de eventos e assinantes
Documente: quando o evento é chamado, o significado dos campos de EventArgs, se precisa desinscrever e quando.
/// <summary>
/// O evento ocorre após o carregamento bem-sucedido dos dados.
/// </summary>
public event EventHandler<DataLoadedEventArgs> DataLoaded;
7. Recomendações resumidas sobre arquitetura de eventos
Separe responsabilidades
O publicador apenas notifica sobre o fato. O assinante decide por si quando assinar e desassinar.
Evite "bombardear" com eventos
Não gere o mesmo evento dezenas de vezes por segundo sem necessidade — isso é carga excessiva.
Prefira não usar eventos para comunicação bidirecional
Eventos são para o padrão "um notifica — muitos escutam". Para comunicação bidirecional considere interfaces, callbacks ou outros mecanismos.
Não mantenha referências explícitas aos assinantes na classe
Não segure referências explícitas aos assinantes — eventos e delegates fazem isso automaticamente.
8. Antipadrões clássicos
Eventos sem tipagem
public event Action<object> SomethingHappened; // Não fica claro o que tem dentro
Ruim: tipagem quebrada, são necessários casts, perde-se a manutenibilidade.
Esquecer do descadastro
public class ShortLivedListener
{
public ShortLivedListener(Publisher p) =>
p.DataReceived += DoWork;
private void DoWork(object sender, EventArgs e) { /* ... */ }
// Sem Dispose, sem desinscrição => objetos zumbi!
}
Violação do SRP
A classe é ao mesmo tempo publicadora, assinante e handler — mistura de responsabilidades. Separe responsabilidades.
9. Aplicação prática em entrevistas e projetos
Em muitos projetos com publicação/assinatura a boa organização de eventos é chave para escalabilidade e manutenção. Em entrevistas geralmente pedem:
- implementar um sistema de eventos com tipagem correta,
- mostrar gerenciamento do ciclo de vida dos assinantes,
- explicar a chamada thread-safe de eventos.
Código de eventos limpo, documentado e corretamente organizado já te destaca entre os candidatos.
GO TO FULL VERSION