J'ai expliqué cette confusion dans un blog à https://www.spicelogic.com/Blog/net-event-handler-memory-leak-16 . Je vais essayer de le résumer ici pour que vous puissiez vous faire une idée claire.
Référence signifie, "Besoin" :
Tout d'abord, vous devez comprendre que, si l'objet A détient une référence à l'objet B, cela signifie que l'objet A a besoin de l'objet B pour fonctionner, n'est-ce pas ? Donc, le ramasseur de déchets ne collectera pas l'objet B tant que l'objet A est vivant dans la mémoire.
+= Moyens, en injectant la référence de l'objet de droite à l'objet de gauche :
La confusion vient de l'opérateur C# +=. Cet opérateur n'indique pas clairement au développeur que le côté droit de cet opérateur injecte en fait une référence à l'objet du côté gauche.
Et en faisant cela, l'objet A pense qu'il a besoin de l'objet B, même si, de votre point de vue, l'objet A ne devrait pas se soucier de savoir si l'objet B vit ou non. Comme l'objet A pense que l'objet B est nécessaire, l'objet A protège l'objet B du ramasseur de déchets tant que l'objet A est en vie. Mais, si vous ne vouliez pas de cette protection donné à l'objet abonné à l'événement, alors, on peut dire qu'une fuite de mémoire s'est produite. Pour souligner cette affirmation, laissez-moi préciser que, dans le monde .NET, il n'y a pas de concept de fuite de mémoire comme dans un programme typique C++ non géré. Mais, comme je l'ai dit, l'objet A protège l'objet B du ramassage des ordures et si ce n'était pas votre intention, alors vous pouvez dire qu'une fuite de mémoire s'est produite parce que l'objet B n'était pas censé vivre dans la mémoire.
Vous pouvez éviter une telle fuite en détachant le gestionnaire d'événement.
Comment prendre une décision ?
Il y a beaucoup d'événements et de gestionnaires d'événements dans l'ensemble de votre code-base. Cela signifie-t-il que vous devez continuer à détacher des gestionnaires d'événements partout ? La réponse est non. Si vous deviez le faire, votre base de code serait vraiment laide et verbeuse.
Vous pouvez plutôt suivre un organigramme simple pour déterminer si un gestionnaire d'événement de détachement est nécessaire ou non.
La plupart du temps, vous constaterez que l'objet abonné aux événements est aussi important que l'objet éditeur d'événements et que les deux sont censés vivre en même temps.
Exemple d'un scénario où vous ne devez pas vous inquiéter
Par exemple, un événement de clic de bouton d'une fenêtre.
Ici, l'éditeur d'événement est le bouton, et l'abonné d'événement est la fenêtre principale. En appliquant cet organigramme, posez une question : la fenêtre principale (abonné à l'événement) est-elle censée être morte avant le bouton (éditeur d'événement) ? De toute évidence, non. Cela n'a même pas de sens. Alors, pourquoi se préoccuper de détacher le gestionnaire d'événement de clic ?
Un exemple où le détachement d'un gestionnaire d'événement est un MUST.
Je vais vous donner un exemple où l'objet abonné est censé être mort avant l'objet éditeur. Disons que votre MainWindow publie un événement nommé "SomethingHappened" et que vous affichez une fenêtre enfant à partir de la fenêtre principale en cliquant sur un bouton. La fenêtre enfant s'abonne à cet événement de la fenêtre principale.
Et la fenêtre enfant s'abonne à un événement de la fenêtre principale.
A partir de ce code, nous pouvons clairement comprendre qu'il y a un bouton dans la fenêtre principale. En cliquant sur ce bouton, une fenêtre enfant apparaît. La fenêtre enfant écoute un événement de la fenêtre principale. Après avoir fait quelque chose, l'utilisateur ferme la fenêtre enfant.
Maintenant, selon l'organigramme que j'ai fourni, si vous posez la question "Est-ce que la fenêtre enfant (abonné à l'événement) est censée être morte avant l'éditeur d'événement (fenêtre principale) ? La réponse devrait être OUI. N'est-ce pas ? Donc, détachez le gestionnaire d'événement. Je le fais habituellement à partir de l'événement Unloaded de la fenêtre.
Une règle de base : Si votre vue (c'est-à-dire WPF, WinForm, UWP, Xamarin Form, etc.) souscrit à un événement d'un ViewModel, pensez toujours à détacher le gestionnaire d'événement. Car un ViewModel vit généralement plus longtemps qu'une vue. Donc, si le ViewModel n'est pas détruit, toute vue qui a souscrit à un événement de ce ViewModel restera en mémoire, ce qui n'est pas bon.
Preuve du concept en utilisant un profileur de mémoire.
Ce ne sera pas très amusant si nous ne pouvons pas valider le concept avec un profileur de mémoire. J'ai utilisé le profileur de mémoire JetBrain dotMemory dans cette expérience.
D'abord, j'ai lancé la MainWindow, qui s'affiche comme ceci :
Puis, j'ai pris un instantané de la mémoire. Puis j'ai cliqué sur le bouton 3 fois . Trois fenêtres enfants sont apparues. J'ai fermé toutes ces fenêtres enfants et cliqué sur le bouton Force GC dans le profiler dotMemory pour m'assurer que le Garbage Collector est appelé. Ensuite, j'ai pris un autre instantané de la mémoire et je l'ai comparé. Et voilà, notre crainte était fondée. La fenêtre enfant n'était pas collectée par le Garbage Collector même après sa fermeture. Non seulement cela mais le nombre d'objets perdus pour l'objet ChildWindow est également indiqué comme " 3 " (J'ai cliqué 3 fois sur le bouton pour afficher 3 fenêtres enfants).
Ok, alors, j'ai détaché le gestionnaire d'événement comme indiqué ci-dessous.
Ensuite, j'ai effectué les mêmes étapes et vérifié le profileur de mémoire. Cette fois, wow ! plus de fuite de mémoire.