Expliquei essa confusão em um blog em https://www.spicelogic.com/Blog/net-event-handler-memory-leak-16 . Vou tentar resumir aqui para que você possa ter uma ideia clara.
Meios de referência, "Necessidade":
Primeiro de tudo, você precisa entender que, se o objeto A tiver uma referência ao objeto B, isso significa que o objeto A precisa do objeto B para funcionar, certo? Portanto, o coletor de lixo não coletará o objeto B enquanto o objeto A estiver vivo na memória.
Eu acho que essa parte deve ser óbvia para um desenvolvedor.
+ = Significa, injetando referência do objeto do lado direito ao objeto da esquerda:
Mas, a confusão vem do operador C # + =. Este operador não diz claramente ao desenvolvedor que, no lado direito, está realmente injetando uma referência ao objeto do lado esquerdo.
E, ao fazer isso, o objeto A pensa, ele precisa do objeto B, embora, na sua perspectiva, o objeto A não deva se importar se o objeto B vive ou não. Como o objeto A pensa que o objeto B é necessário, o objeto A protege o objeto B do coletor de lixo enquanto o objeto A estiver vivo. Mas, se você não deseja que essa proteção seja dada ao objeto de assinante do evento, pode-se dizer que ocorreu um vazamento de memória.
Você pode evitar esse vazamento desanexando o manipulador de eventos.
Como tomar uma decisão?
Mas há muitos eventos e manipuladores de eventos em toda a sua base de código. Isso significa que você precisa desanexar os manipuladores de eventos em todos os lugares? A resposta é não. Se você tivesse que fazer isso, sua base de código será realmente feia com detalhes.
Você pode seguir um fluxograma simples para determinar se um manipulador de eventos de desanexação é necessário ou não.
Na maioria das vezes, você pode achar que o objeto do assinante do evento é tão importante quanto o objeto do publicador do evento e que ambos devem estar vivendo ao mesmo tempo.
Exemplo de um cenário em que você não precisa se preocupar
Por exemplo, um botão clique no evento de uma janela.
Aqui, o publicador do evento é o Button e o assinante do evento é a MainWindow. Ao aplicar esse fluxograma, faça uma pergunta: a Janela Principal (assinante do evento) deveria estar morta antes do Button (editor do evento)? Obviamente não. Certo? Isso nem faz sentido. Então, por que se preocupar em desanexar o manipulador de eventos click?
Um exemplo quando um desanexamento de manipulador de eventos é OBRIGATÓRIO.
Vou fornecer um exemplo em que o objeto do assinante deve estar morto antes do objeto do publicador. Digamos, sua MainWindow publica um evento chamado "SomethingHappened" e você mostra uma janela filho da janela principal com um clique no botão. A janela filho se inscreve no evento da janela principal.
E, a janela filho se inscreve em um evento da Janela Principal.
A partir desse código, podemos entender claramente que existe um botão na janela principal. Clicar nesse botão mostra uma janela filho. A janela filho ouve um evento na janela principal. Depois de fazer algo, o usuário fecha a janela filho.
Agora, de acordo com o fluxograma que forneço se você fizer uma pergunta "A janela filho (assinante do evento) deveria estar morta antes do publicador do evento (janela principal)? A resposta deveria ser SIM. Certo? Então, desconecte o manipulador de eventos Eu costumo fazer isso a partir do evento Unloaded da janela.
Uma regra prática : se sua visualização (por exemplo, WPF, WinForm, UWP, Xamarin Form etc.) se inscrever em um evento de um ViewModel, lembre-se sempre de desanexar o manipulador de eventos. Como um ViewModel geralmente dura mais que uma visualização. Portanto, se o ViewModel não for destruído, qualquer visualização do evento inscrito desse ViewModel permanecerá na memória, o que não é bom.
Prova do conceito usando um perfilador de memória.
Não será muito divertido se não pudermos validar o conceito com um criador de perfil de memória. Eu usei o profiler JetBrain dotMemory nesta experiência.
Primeiro, eu executei o MainWindow, que aparece assim:
Então, tirei um instantâneo de memória. Então eu cliquei no botão 3 vezes . Três janelas de crianças apareceram. Fechei todas essas janelas filhas e cliquei no botão Forçar GC no profiler dotMemory para garantir que o Garbage Collector seja chamado. Depois, peguei outro instantâneo de memória e o comparei. Ver! nosso medo era verdadeiro. A janela filho não foi coletada pelo coletor de lixo mesmo depois que elas foram fechadas. Não apenas isso, mas a contagem de objetos vazados para o objeto ChildWindow também é mostrada " 3 " (cliquei no botão 3 vezes para mostrar 3 janelas filho).
Ok, então, desanexei o manipulador de eventos, como mostrado abaixo.
Depois, executei as mesmas etapas e verifiquei o perfil de memória. Desta vez, uau! não há mais vazamento de memória.