1. Introdução
Trabalhar com delegates e events em C# é agradável e conveniente — a linguagem faz muita coisa por você. Contudo, por trás dessa fachada há vários obstáculos: leaks invisíveis de memória, bugs estranhos por inscrição dupla, dessíncronia de handlers e até exceções súbitas durante a dispatch de notificações. Delegates e events são poderosos, mas exigem cuidado com o ciclo de vida dos objetos, entendimento de threads e de como exatamente funcionam chamadas e desinscrições. Se seus events funcionam "quase sempre", mas às vezes não disparam ou causam erros estranhos — você não está sozinho! Vamos ver onde até programadores experientes mais erram e como evitar isso.
Tabela de erros principais
| Erro | Consequência | Como evitar |
|---|---|---|
| Inscrição dupla | Handler é chamado múltiplas vezes | Acompanhar inscrições, remover antes de adicionar |
| Não desinscrever (vazamento de memória) | Subscribers ficam na memória, “zumbis” | Sempre desinscrever, usar IDisposable |
| Exceção no handler | Handlers restantes não serão chamados | try/catch nos handlers ou iterar manualmente |
| Modificar assinantes durante o event | Perda ou duplicação de chamadas | Iterar sobre uma cópia dos handlers (GetInvocationList()) |
| Chamar evento quando MyEvent == null | NullReferenceException | Checar por null, usar ?.Invoke |
| Assinatura do handler não bate | Erro de compilação | Verificar assinatura |
| Chamar evento fora da classe publisher | Erro de compilação | Chamar só via OnEventName |
| Eventos static no lugar errado | Mix de assinaturas | Não fazer static sem necessidade |
| Problemas de closure com lambdas | Valores inesperados | Fazer cópia da variável |
2. Inscrição múltipla e chamadas múltiplas
Essência do erro
Se você inscrever o mesmo handler várias vezes em um mesmo event, cada += adiciona seu método na fila do delegate. Como resultado, o handler será chamado tantas vezes quanto foi adicionado.
Como isso aparece?
Imagina que você tem um botão e um handler de click:
Button btn = new Button();
btn.Click += OnButtonClick; // Vamos inscrever
btn.Click += OnButtonClick; // E aqui temos inscrição duplicada!
Agora, a cada clique o método OnButtonClick será chamado duas vezes. Se dentro do handler você, por exemplo, atualiza um contador ou escreve no log, verá resultados dobrados.
Como consertar?
Normalmente a inscrição duplicada acontece por violação da estrutura do código — por exemplo, se o += está em um método que é chamado várias vezes (em diferentes ciclos de vida da form).
- Monitore onde a inscrição acontece.
- Não coloque += em etapas do ciclo de vida que podem ser chamadas múltiplas vezes.
- Às vezes é útil garantir uma inscrição “única” — antes de adicionar o handler, remova-o:
myEvent -= MyHandler; // Remove só por segurança
myEvent += MyHandler; // Depois inscreve de novo
Isso é seguro: se o handler ainda não existia, o -= não faz nada.
3. Subscriber zumbi
Essência do erro
Se um subscriber se inscreve em um publisher de longa vida e não se desinscreve, o GC não vai coletá‑lo: o publisher ainda mantém referência ao delegate do handler, logo — ao objeto subscriber. Resultado — leak de memória.
Exemplo típico
public class TemporaryPopup : IDisposable
{
private Window _hostWindow;
public TemporaryPopup(Window window)
{
_hostWindow = window;
_hostWindow.Closed += OnHostClosed;
}
private void OnHostClosed(object sender, EventArgs e)
{
// ...
}
public void Dispose()
{
_hostWindow.Closed -= OnHostClosed; // Não esqueça de desinscrever!
}
}
Se você esquecer de chamar Dispose() ou não o invocar, mesmo removendo todas as referências ao TemporaryPopup, o objeto não será coletado — a janela ainda referencia seu handler.
Como evitar?
- Implemente IDisposable nos subscribers se o ciclo de vida deles for mais curto que o do publisher.
- Use o padrão using ou chame explicitamente Dispose():
using (var popup = new TemporaryPopup(mainWindow))
{
// ...
} // Aqui Dispose é chamado automaticamente
Em apps GUI — desinscreva ao fechar a janela/form (por exemplo, nos handlers de fechamento ou no Dispose() da form).
4. Tratamento de exceções dentro dos handlers
Essência do erro
Quando um event invoca dezenas de handlers e um deles lança uma exceção, os outros handlers não serão executados.
Demonstração
public event EventHandler MyEvent;
public void Raise()
{
MyEvent?.Invoke(this, EventArgs.Empty);
}
Se um dos métodos inscritos em MyEvent lançar uma exceção, os demais não serão chamados — a cadeia é interrompida.
Como lidar com isso?
- Nos handlers trate exceções localmente (try/catch) ou deixe explicitamente propagar.
- Em cenários complexos — itere manualmente pelos subscribers e isole erros de cada um:
var handlers = MyEvent?.GetInvocationList();
foreach (var handler in handlers)
{
try
{
handler.DynamicInvoke(this, EventArgs.Empty);
}
catch (Exception ex)
{
// Logging, recuperação de emergência
}
}
5. Modificar a lista de assinantes durante a dispatch
Essência do erro
Se um handler se desinscreve ou inscreve outros handlers enquanto o evento está sendo enviado, isso pode corromper a ordem de chamadas: alguns handlers podem ser pulados ou chamados duas vezes.
Como evitar?
- Não modifique assinaturas a partir de handlers.
- Se precisar, itere sobre uma cópia da lista de delegates:
var handlers = MyEvent?.GetInvocationList();
foreach (EventHandler handler in handlers)
{
handler(this, EventArgs.Empty);
}
6. Confusão com valor null do delegate (sem assinantes)
Essência do erro
Se ninguém está inscrito no event, seu delegate é null. Chamar sem checar causa NullReferenceException.
Exemplo (ruim)
public event EventHandler MyEvent;
public void Raise()
{
MyEvent(this, EventArgs.Empty); // Se não houver assinantes — exceção!
}
Como fazer certo?
- Use chamada segura: MyEvent?.Invoke(this, EventArgs.Empty).
- Ou use o clássico padrão thread‑safe: copie o delegate para uma variável local e invoque-a.
7. Misturar delegates/events com assinaturas diferentes
Essência do erro
Delegates são estritamente tipados. Mismatch entre a assinatura do método handler e do delegate do event é erro de compilação.
Exemplo
public event EventHandler<string> TextChanged;
void WrongHandler(object sender, int number) { /* ... */ }
TextChanged += WrongHandler; // Erro de compilação!
Use os delegates padrão EventHandler e EventHandler<T>, e verifique o perfeito alinhamento das assinaturas.
8. Tentar invocar evento fora da classe-publisher
Essência do erro
event é um delegate encapsulado: código externo não pode invocá‑lo diretamente (só pode adicionar/remover handlers).
Exemplo
public class MyPublisher
{
public event EventHandler SomethingHappened;
}
var publisher = new MyPublisher();
publisher.SomethingHappened?.Invoke(publisher, EventArgs.Empty); // Erro!
Como proceder?
A invocação deve ser feita via método protegido/público do publisher — normalmente OnEventName. De fora — apenas +=/-=.
9. Erros com accessors add e remove
Essência do erro
Custom accessors para events permitem controlar a inscrição, mas é fácil quebrar a ordem correta de chamadas ou a thread‑safety.
Exemplo
public event EventHandler MyEvent
{
add { /* ... */ }
remove { /* ... */ }
}
Se não tiver certeza — use a implementação padrão de events. Se implementar manualmente, consulte a documentação e cuide da sincronização.
10. Acesso a events de contextos static e instance
Essência do erro
É fácil declarar por engano um event static quando ele deveria ser por instância. Aí todos os objetos compartilham a mesma fila de subscribers.
Exemplo
public static event EventHandler GlobalEvent; // Oops!
// Instâncias perdem individualidade, inscrições se misturam
Como evitar?
Faça um event static só quando realmente for um nível global (por exemplo, log global). Na maior parte dos casos, mantenha encapsulação por instância.
11. Problemas com captura de variáveis em lambdas
Essência do erro
Lambdas capturam variáveis por referência. Em loops isso geralmente leva ao efeito do “último valor”.
Exemplo
for (int i = 0; i < 5; i++)
{
button.Click += (s, e) => Console.WriteLine(i);
}
// Todos os handlers vão imprimir "5"
Como fazer certo?
for (int i = 0; i < 5; i++)
{
int copy = i; // Cópia local
button.Click += (s, e) => Console.WriteLine(copy);
}
12. Mistura de referências fracas e fortes: Advanced “Weak Events”
Em apps grandes (por exemplo, WPF) usa‑se o mecanismo de “weak events”, onde o publisher mantém uma weak reference ao subscriber para não impedir o GC. Weak events ajudam contra leaks, mas o subscriber pode ser coletado e deixar de receber o evento.
Detalhes: Weak Event Patterns (MSDN)
13. Falta de padrão de naming e assinaturas de events
Essência do erro
Siga assinaturas e nomes padrão: events no passado (Changed, Closed, Completed), e args derivando de EventArgs.
Exemplo “errado”:
public delegate void SomethingHappens(int what);
// ...
public event SomethingHappens Something;
Exemplo “certo”:
public event EventHandler<EventArgs> SomethingHappened;
Para seus events, quase sempre use EventHandler ou EventHandler<T>. Seus colegas (e o você do futuro) vão agradecer.
GO TO FULL VERSION