ACTUALIZAR
A partir de C # 6, la respuesta a esta pregunta es:
SomeEvent?.Invoke(this, e);
Frecuentemente escucho / leo los siguientes consejos:
Siempre haga una copia de un evento antes de verificarlo null
y dispararlo. Esto eliminará un problema potencial con el enhebrado donde el evento se convierte null
en la ubicación justo entre el lugar donde verifica si es nulo y el lugar donde dispara el evento:
// Copy the event delegate before checking/calling
EventHandler copy = TheEvent;
if (copy != null)
copy(this, EventArgs.Empty); // Call any handlers on the copied list
Actualizado : al leer sobre optimizaciones, pensé que esto también podría requerir que el miembro del evento sea volátil, pero Jon Skeet afirma en su respuesta que el CLR no optimiza la copia.
Pero mientras tanto, para que este problema ocurra, otro hilo debe haber hecho algo como esto:
// 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...
La secuencia real podría ser esta mezcla:
// 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
El punto es que se OnTheEvent
ejecuta después de que el autor se ha dado de baja, y sin embargo, simplemente se dieron de baja específicamente para evitar que eso suceda. Sin duda, lo que realmente se necesita es una aplicación de eventos personalizado con la sincronización adecuada en el add
y remove
descriptores de acceso. Además, existe el problema de posibles puntos muertos si se mantiene un bloqueo mientras se dispara un evento.
Entonces, ¿es esta programación de Cargo Cult ? Parece así: muchas personas deben estar dando este paso para proteger su código de múltiples hilos, cuando en realidad me parece que los eventos requieren mucho más cuidado que esto antes de que puedan usarse como parte de un diseño de subprocesos múltiples . En consecuencia, las personas que no están tomando esa atención adicional podrían ignorar este consejo: simplemente no es un problema para los programas de subproceso único y, de hecho, dada la ausencia de la volatile
mayoría de los códigos de ejemplo en línea, el consejo puede no tener efecto en absoluto.
(¿Y no es mucho más simple asignar el vacío delegate { }
en la declaración del miembro para que nunca tenga que verificarlo null
en primer lugar?)
Actualizado:En caso de que no estuviera claro, comprendí la intención del consejo: evitar una excepción de referencia nula en todas las circunstancias. Mi punto es que esta excepción de referencia nula en particular solo puede ocurrir si otro subproceso está excluido del evento, y la única razón para hacerlo es asegurarse de que no se recibirán más llamadas a través de ese evento, lo que claramente NO se logra con esta técnica . Estaría ocultando una condición de carrera, ¡sería mejor revelarla! Esa excepción nula ayuda a detectar un abuso de su componente. Si desea que su componente esté protegido contra el abuso, puede seguir el ejemplo de WPF: almacene la ID del hilo en su constructor y luego arroje una excepción si otro hilo intenta interactuar directamente con su componente. O bien, implemente un componente verdaderamente seguro para subprocesos (no es una tarea fácil).
Por lo tanto, afirmo que simplemente hacer este idioma de copiar / verificar es programación de culto de carga, agregando desorden y ruido a su código. Para protegerse realmente contra otros hilos se requiere mucho más trabajo.
Actualización en respuesta a las publicaciones del blog de Eric Lippert:
Así que hay una cosa importante que me he perdido de los controladores de eventos: "los controladores de eventos deben ser robustos para ser llamados incluso después de que el evento se haya dado de baja", y obviamente, por lo tanto, solo debemos preocuparnos por la posibilidad del evento ser delegado null
. ¿Se documenta ese requisito en los controladores de eventos en alguna parte?
Y así: "Hay otras formas de resolver este problema; por ejemplo, inicializar el controlador para que tenga una acción vacía que nunca se elimine. Pero hacer una comprobación nula es el patrón estándar".
Entonces, el fragmento restante de mi pregunta es, ¿por qué es explícita-null-check el "patrón estándar"? La alternativa, asignar el delegado vacío, solo requiere = delegate {}
agregarse a la declaración del evento, y esto elimina esas pequeñas pilas de ceremonia apestosa de cada lugar donde se realiza el evento. Sería fácil asegurarse de que el delegado vacío sea barato de instanciar. ¿O todavía me falta algo?
Seguramente debe ser que (como sugirió Jon Skeet) este es solo un consejo de .NET 1.x que no se ha extinguido, como debería haberlo hecho en 2005.
EventName(arguments)
invocar al delegado del evento incondicionalmente, en lugar de que solo invoque al delegado si no es nulo (no haga nada si es nulo).