J'ai expliqué cette confusion dans un blog à l' adresse https://www.spicelogic.com/Blog/net-event-handler-memory-leak-16 . Je vais essayer de le résumer ici pour que vous puissiez avoir une idée claire.
Référence signifie "Besoin":
Tout d'abord, vous devez comprendre que si l'objet A contient une référence à l'objet B, cela signifiera que l'objet A a besoin de l'objet B pour fonctionner, n'est-ce pas? Ainsi, le garbage collector ne collectera pas l'objet B tant que l'objet A est vivant dans la mémoire.
Je pense que cette partie devrait être évidente pour un développeur.
+ = Signifie, injecter la référence de l'objet côté droit à l'objet gauche:
Mais, 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 ce faisant, l'objet A pense, 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 ramasse-miettes tant que l'objet A est vivant. Mais, si vous ne vouliez pas que cette protection soit donnée à l'objet d'abonné d'événement, vous pouvez dire qu'une fuite de mémoire s'est produite.
Vous pouvez éviter une telle fuite en détachant le gestionnaire d'événements.
Comment prendre une décision?
Mais, il y a beaucoup d'événements et de gestionnaires d'événements dans toute votre base de code. Cela signifie-t-il que vous devez continuer à détacher les gestionnaires d'événements partout? La réponse est non. Si vous deviez le faire, votre base de code sera vraiment moche 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 pouvez trouver que l'objet abonné à l'événement est aussi important que l'objet éditeur d'événement et que les deux sont censés vivre en même temps.
Exemple de scénario où vous n'avez pas à vous inquiéter
Par exemple, un événement de clic sur un bouton d'une fenêtre.
Ici, l'éditeur d'événement est le Button et l'abonné à l'événement est MainWindow. 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 de l'événement)? De toute évidence, non? Cela n'a même pas de sens. Alors, pourquoi s'inquiéter de détacher le gestionnaire d'événements de clic?
Un exemple où un détachement de gestionnaire d'événements est un MUST.
Je vais 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 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.
À partir de ce code, nous pouvons clairement comprendre qu'il existe un bouton dans la fenêtre principale. Cliquez sur ce bouton pour afficher une fenêtre enfant. La fenêtre enfant écoute un événement depuis 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 une question "La fenêtre enfant (abonné à l'événement) est-elle censée être morte avant l'éditeur de l'événement (fenêtre principale)? La réponse devrait être OUI. D'accord? Alors, détachez le gestionnaire d'événements Je fais généralement cela à partir de l'événement Unloaded de la fenêtre.
Règle générale: si votre vue (par exemple WPF, WinForm, UWP, Xamarin Form, etc.) s'abonne à un événement d'un ViewModel, n'oubliez pas de détacher le gestionnaire d'événements. Parce qu'un ViewModel dure généralement plus longtemps qu'une vue. Ainsi, si le ViewModel n'est pas détruit, toute vue qui a souscrit l'événement de ce ViewModel restera en mémoire, ce qui n'est pas bon.
Preuve du concept à l'aide d'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 JetBrain dotMemory dans cette expérience.
Tout d'abord, j'ai exécuté MainWindow, qui apparaît comme ceci:
Ensuite, j'ai pris un instantané de la mémoire. Ensuite, j'ai cliqué 3 fois sur le bouton . Trois fenêtres enfants sont apparues. J'ai fermé toutes ces fenêtres enfants et cliqué sur le bouton Forcer GC dans le profileur 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é. Voir! notre peur était vraie. La fenêtre enfant n'a pas été collectée par le garbage collector même après leur fermeture. Non seulement cela, mais le nombre d'objets ayant subi une fuite pour l'objet ChildWindow est également affiché " 3 " (j'ai cliqué sur le bouton 3 fois pour afficher 3 fenêtres enfants).
Ok, alors, j'ai détaché le gestionnaire d'événements 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.