METTRE À JOUR
Depuis C # 6, la réponse à cette question est:
SomeEvent?.Invoke(this, e);
J'entends / lis fréquemment les conseils suivants:
Faites toujours une copie d'un événement avant de le vérifier null
et de le déclencher. Cela éliminera un problème potentiel avec le filetage où l'événement se situe null
à l'emplacement exact entre l'endroit où vous vérifiez la valeur null et l'endroit où vous déclenchez l'événement:
// Copy the event delegate before checking/calling
EventHandler copy = TheEvent;
if (copy != null)
copy(this, EventArgs.Empty); // Call any handlers on the copied list
Mise à jour : J'ai pensé à la lecture des optimisations que cela pourrait également nécessiter la volatilité du membre de l'événement, mais Jon Skeet déclare dans sa réponse que le CLR n'optimise pas la copie.
Mais en attendant, pour que ce problème se produise, un autre thread doit avoir fait quelque chose comme ceci:
// 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 séquence réelle pourrait être ce mélange:
// 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
Le fait étant que OnTheEvent
l'auteur s'exécute après sa désinscription, et pourtant ils se sont simplement désabonnés spécifiquement pour éviter que cela ne se produise. Ce qui est vraiment nécessaire, c'est une implémentation d'événements personnalisée avec une synchronisation appropriée dans les accesseurs add
et remove
. De plus, il y a le problème des interblocages possibles si un verrou est maintenu pendant le déclenchement d'un événement.
Alors est - ce Programming Cargo Cult ? Il semble que de cette façon - beaucoup de gens doivent prendre cette mesure pour protéger leur code de plusieurs threads, alors qu'en réalité il me semble que les événements nécessitent beaucoup plus de soin que cela avant de pouvoir être utilisés dans le cadre d'une conception multi-thread . Par conséquent, les personnes qui ne prennent pas ces précautions supplémentaires pourraient tout aussi bien ignorer ce conseil - ce n'est tout simplement pas un problème pour les programmes à un seul thread, et en fait, étant donné l'absence de la volatile
plupart des exemples de code en ligne, le conseil peut ne pas avoir effet du tout.
(Et n'est-il pas beaucoup plus simple d'affecter simplement le vide delegate { }
sur la déclaration de membre afin que vous n'ayez jamais besoin de vérifier null
en premier lieu?)
Actualisé:Au cas où ce ne serait pas clair, j'ai bien compris l'intention de l'avis - éviter une exception de référence nulle en toutes circonstances. Mon point est que cette exception de référence nulle particulière ne peut se produire que si un autre thread est retiré de l'événement, et la seule raison de le faire est de s'assurer qu'aucun autre appel ne sera reçu via cet événement, ce qui n'est clairement PAS atteint par cette technique. . Vous cacheriez une condition de course - il vaudrait mieux la révéler! Cette exception nulle permet de détecter un abus de votre composant. Si vous souhaitez que votre composant soit protégé contre les abus, vous pouvez suivre l'exemple de WPF - stocker l'ID de thread dans votre constructeur, puis lever une exception si un autre thread tente d'interagir directement avec votre composant. Ou bien implémentez un composant vraiment thread-safe (pas une tâche facile).
Je soutiens donc que le simple fait de faire cet idiome de copie / vérification est une programmation culte, ajoutant du désordre et du bruit à votre code. Pour se protéger contre d'autres threads, il faut beaucoup plus de travail.
Mise à jour en réponse aux articles de blog d'Eric Lippert:
Il y a donc une chose importante que j'ai manquée à propos des gestionnaires d'événements: "les gestionnaires d'événements doivent être robustes face à être appelés même après que l'événement a été désabonné", et évidemment, nous n'avons donc qu'à nous soucier de la possibilité de l'événement être délégué null
. Cette exigence sur les gestionnaires d'événements est-elle documentée n'importe où?
Et donc: "Il existe d'autres façons de résoudre ce problème; par exemple, initialiser le gestionnaire pour avoir une action vide qui n'est jamais supprimée. Mais faire une vérification nulle est le modèle standard."
Donc, le dernier fragment de ma question est, pourquoi est-null-check explicite le "modèle standard"? L'alternative, attribuer le délégué vide, ne nécessite que = delegate {}
d'être ajoutée à la déclaration de l'événement, ce qui élimine ces petits tas de cérémonie puante de chaque endroit où l'événement est déclenché. Il serait facile de s'assurer que le délégué vide est bon marché à instancier. Ou est-ce que je manque encore quelque chose?
Il doit sûrement s'agir (comme l'a suggéré Jon Skeet) d'un conseil .NET 1.x qui n'a pas disparu, comme il aurait dû le faire en 2005?
EventName(arguments)
invoquer le délégué de l'événement sans condition, plutôt que de ne l'invoquer que s'il n'est pas nul (ne rien faire s'il est nul).