ATUALIZAR
A partir do C # 6, a resposta para esta pergunta é:
SomeEvent?.Invoke(this, e);
Frequentemente, ouço / leio os seguintes conselhos:
Sempre faça uma cópia de um evento antes de verificar null
e acioná-lo. Isso eliminará um possível problema com a segmentação onde o evento se torna null
no local entre o local em que você verifica nulo e o local em que o evento é disparado:
// Copy the event delegate before checking/calling
EventHandler copy = TheEvent;
if (copy != null)
copy(this, EventArgs.Empty); // Call any handlers on the copied list
Atualizado : pensei em ler sobre otimizações que isso também pode exigir que o membro do evento seja volátil, mas Jon Skeet afirma em sua resposta que o CLR não otimiza a cópia.
Entretanto, para que esse problema ocorra, outro segmento deve ter feito algo assim:
// Better delist from event - don't want our handler called from now on:
otherObject.TheEvent -= OnTheEvent;
// Good, now we can be certain that OnTheEvent will not run...
A sequência real pode ser essa mistura:
// Copy the event delegate before checking/calling
EventHandler copy = TheEvent;
// Better delist from event - don't want our handler called from now on:
otherObject.TheEvent -= OnTheEvent;
// Good, now we can be certain that OnTheEvent will not run...
if (copy != null)
copy(this, EventArgs.Empty); // Call any handlers on the copied list
O ponto é que OnTheEvent
é executado após o cancelamento da assinatura do autor e, no entanto, eles simplesmente cancelam a assinatura especificamente para evitar que isso aconteça. Certamente o que é realmente necessário é uma implementação de evento personalizada com sincronização apropriada nos acessadores add
e remove
. Além disso, há o problema de possíveis conflitos se um bloqueio for mantido enquanto um evento é disparado.
Então, isso é programação de culto à carga ? Parece que sim - muitas pessoas devem seguir esta etapa para proteger seu código de vários threads, quando na realidade me parece que os eventos exigem muito mais cuidado do que isso antes que possam ser usados como parte de um design com vários threads . Consequentemente, as pessoas que não estão tomando esse cuidado adicional também podem ignorar esse conselho - simplesmente não é um problema para programas de thread único e, de fato, dada a ausência do volatile
código de exemplo on-line, o conselho pode não ter efeito em tudo.
(E não é muito mais simples atribuir o vazio delegate { }
na declaração do membro para que você nunca precise procurar null
em primeiro lugar?)
Atualizada:Caso não estivesse claro, compreendi a intenção do conselho - evitar uma exceção de referência nula em todas as circunstâncias. O que quero dizer é que essa exceção de referência nula em particular só pode ocorrer se outro segmento for excluído do evento, e a única razão para isso é garantir que nenhuma outra chamada seja recebida por esse evento, o que claramente NÃO é alcançado por essa técnica. . Você estaria escondendo uma condição de corrida - seria melhor revelá-la! Essa exceção nula ajuda a detectar um abuso do seu componente. Se você deseja que seu componente seja protegido contra abuso, siga o exemplo do WPF - armazene o ID do encadeamento em seu construtor e, em seguida, lance uma exceção se outro encadeamento tentar interagir diretamente com seu componente. Ou então, implemente um componente verdadeiramente seguro para threads (não é uma tarefa fácil).
Por isso, afirmo que apenas fazer esse idioma de cópia / verificação é uma programação de cultos de carga, adicionando bagunça e ruído ao seu código. Para realmente proteger contra outros threads, é necessário muito mais trabalho.
Atualização em resposta às postagens do blog de Eric Lippert:
Portanto, havia uma coisa importante que eu tinha perdido nos manipuladores de eventos: "é necessário que os manipuladores de eventos sejam robustos mesmo após serem cancelados", e obviamente, portanto, precisamos apenas nos preocupar com a possibilidade do evento delegar ser null
. Esse requisito nos manipuladores de eventos está documentado em algum lugar?
E assim: "Existem outras maneiras de resolver esse problema; por exemplo, inicializar o manipulador para ter uma ação vazia que nunca é removida. Mas fazer uma verificação nula é o padrão padrão".
Portanto, o único fragmento restante da minha pergunta é: por que o explícito-nulo-verifica o "padrão padrão"? A alternativa, atribuir o delegado vazio, exige apenas = delegate {}
que seja adicionada à declaração do evento, e isso elimina aquelas pequenas pilhas de cerimônia fedida de todos os lugares onde o evento é gerado. Seria fácil garantir que o delegado vazio seja barato para instanciar. Ou ainda estou faltando alguma coisa?
Certamente deve ser que (como Jon Skeet sugeriu) este é apenas o conselho do .NET 1.x que não desapareceu, como deveria ter ocorrido em 2005?
EventName(arguments)
invocar o delegado do evento incondicionalmente, em vez de apenas invocar o delegado se não for nulo (não faça nada se for nulo).