Ho spiegato questa confusione in un blog su https://www.spicelogic.com/Blog/net-event-handler-memory-leak-16 . Cercherò di riassumere qui in modo che tu possa avere un'idea chiara.
Riferimento significa "Bisogno":
Prima di tutto, devi capire che, se l'oggetto A contiene un riferimento all'oggetto B, allora, significa che l'oggetto A ha bisogno dell'oggetto B per funzionare, giusto? Pertanto, il Garbage Collector non raccoglierà l'oggetto B finché l'oggetto A è vivo nella memoria.
Penso che questa parte dovrebbe essere ovvia per uno sviluppatore.
+ = Mezzi, iniettando riferimento dell'oggetto lato destro all'oggetto sinistro:
Ma la confusione viene dall'operatore C # + =. Questo operatore non dice chiaramente allo sviluppatore che, il lato destro di questo operatore sta effettivamente iniettando un riferimento all'oggetto lato sinistro.
E così facendo, l'oggetto A pensa, ha bisogno dell'oggetto B, anche se, dalla tua prospettiva, all'oggetto A non dovrebbe importare se l'oggetto B vive o no. Poiché l'oggetto A ritiene che sia necessario l'oggetto B, l'oggetto A protegge l'oggetto B dal cestino della spazzatura fintanto che l'oggetto A è vivo. Ma, se non si desidera che venga data la protezione all'oggetto dell'abbonato all'evento, si può dire che si è verificata una perdita di memoria.
È possibile evitare tale perdita staccando il gestore eventi.
Come prendere una decisione?
Ma ci sono molti eventi e gestori di eventi nell'intera base di codice. Significa che devi continuare a staccare i gestori di eventi ovunque? La risposta è No. Se dovessi farlo, la tua base di codice sarà davvero brutta con i dettagli.
Puoi piuttosto seguire un semplice diagramma di flusso per determinare se un gestore di eventi distaccante è necessario o meno.
La maggior parte delle volte, potresti scoprire che l'oggetto dell'abbonato all'evento è importante quanto l'oggetto del publisher dell'evento ed entrambi dovrebbero vivere allo stesso tempo.
Esempio di uno scenario in cui non devi preoccuparti
Ad esempio, un evento clic pulsante di una finestra.
Qui, l'editore dell'evento è il pulsante e l'abbonato all'evento è la finestra principale. Applicando quel diagramma di flusso, ponendo una domanda, la finestra principale (abbonato all'evento) dovrebbe essere morta prima del pulsante (editore dell'evento)? Ovviamente no, vero? Non ha nemmeno senso. Quindi, perché preoccuparsi di staccare il gestore eventi click?
Un esempio in cui il distacco di un gestore di eventi è DEVE.
Fornirò un esempio in cui l'oggetto sottoscrittore dovrebbe essere morto prima dell'oggetto editore. Supponiamo che la tua MainWindow pubblichi un evento chiamato "SomethingHappened" e mostri una finestra figlio dalla finestra principale facendo clic su un pulsante. La finestra figlio si iscrive a quell'evento della finestra principale.
E la finestra figlio si iscrive a un evento della finestra principale.
Da questo codice, possiamo capire chiaramente che c'è un pulsante nella finestra principale. Facendo clic su quel pulsante viene visualizzata una finestra figlio. La finestra figlio ascolta un evento dalla finestra principale. Dopo aver fatto qualcosa, l'utente chiude la finestra figlio.
Ora, secondo il diagramma di flusso che ho fornito se fai una domanda "La finestra figlio (abbonato all'evento) dovrebbe essere morta prima che l'editore dell'evento (finestra principale)? La risposta dovrebbe essere SÌ. Giusto? Quindi, staccare il gestore dell'evento Di solito lo faccio dall'evento Unloaded della finestra.
Una regola empirica: se la tua vista (ad es. WPF, WinForm, UWP, Xamarin Form, ecc.) Si iscrive a un evento di un ViewModel, ricordati sempre di staccare il gestore dell'evento. Perché un ViewModel di solito è più lungo di una vista. Quindi, se ViewModel non viene distrutto, qualsiasi vista dell'evento sottoscritto di quel ViewModel rimarrà in memoria, il che non va bene.
Prova del concetto usando un profiler di memoria.
Non sarà molto divertente se non possiamo convalidare il concetto con un profiler di memoria. Ho usato il profiler JetBrain dotMemory in questo esperimento.
Innanzitutto, ho eseguito MainWindow, che si presenta in questo modo:
Quindi, ho preso un'istantanea della memoria. Quindi ho fatto clic sul pulsante 3 volte . Sono apparse tre finestre per bambini. Ho chiuso tutte quelle finestre secondarie e ho fatto clic sul pulsante Force GC nel profiler dotMemory per assicurarmi che venga chiamato Garbage Collector. Quindi, ho preso un'altra istantanea di memoria e l'ho confrontata. Ecco! la nostra paura era vera. La finestra figlio non è stata raccolta dal Garbage Collector anche dopo che sono stati chiusi. Non solo, ma anche il conteggio degli oggetti trapelati per l'oggetto ChildWindow viene mostrato " 3 " (ho fatto clic sul pulsante 3 volte per mostrare 3 finestre secondarie ).
Ok, quindi, ho rimosso il gestore eventi come mostrato di seguito.
Quindi, ho eseguito gli stessi passaggi e verificato il profiler della memoria. Questa volta, wow! non più perdite di memoria.