Como os events causam vazamentos de memory em C # e como as Referências Fraco ajudam a atenuar isso?

Há duas maneiras (que eu conheço) para causar um memory leaks não intencional em C #:

  1. Não dispor de resources que implementam IDisposable
  2. Referência e desconexão de events incorretamente.

Eu realmente não entendo o segundo ponto. Se o object de origem tiver uma vida útil maior que o ouvinte e o ouvinte não precisar mais dos events quando não houver outras referências a ele, o uso de events .NET normais causará um memory leaks: o object de origem mantém objects ouvinte na memory deve ser coletado de lixo.

Você pode explicar como events podem causar vazamentos de memory com código em C # e como eu posso codificar para contornar isso usando referências fracas e sem referências fracas?

Quando um ouvinte anexa um ouvinte de evento a um evento, o object de origem obterá uma referência ao object ouvinte. Isso significa que o listener não pode ser coletado pelo coletor de lixo até que o manipulador de events seja desanexado ou o object de origem seja coletado.

Considere as seguintes classs:

 class Source { public event EventHandler SomeEvent; } class Listener { public Listener(Source source) { // attach an event listner; this adds a reference to the // source_SomeEvent method in this instance to the invocation list // of SomeEvent in source source.SomeEvent += new EventHandler(source_SomeEvent); } void source_SomeEvent(object sender, EventArgs e) { // whatever } } 

… e depois o seguinte código:

 Source newSource = new Source(); Listener listener = new Listener(newSource); listener = null; 

Apesar de atribuirmos null ao listener , ele não estará qualificado para a garbage collection, já que newSource ainda mantém uma referência ao manipulador de events ( Listener.source_SomeEvent ). Para corrigir esse tipo de vazamento, é importante sempre desappend os ouvintes de events quando eles não forem mais necessários.

O exemplo acima é escrito para se concentrar no problema com o vazamento. Para corrigir esse código, o mais fácil talvez seja permitir que o Listener mantenha uma referência à Source , para que ele possa mais tarde desappend o ouvinte de evento:

 class Listener { private Source _source; public Listener(Source source) { _source = source; // attach an event listner; this adds a reference to the // source_SomeEvent method in this instance to the invocation list // of SomeEvent in source _source.SomeEvent += source_SomeEvent; } void source_SomeEvent(object sender, EventArgs e) { // whatever } public void Close() { if (_source != null) { // detach event handler _source.SomeEvent -= source_SomeEvent; _source = null; } } } 

Em seguida, o código de chamada pode sinalizar que é feito usando o object, que irá remover a referência que a Source tem para ‘Listener’;

 Source newSource = new Source(); Listener listener = new Listener(newSource); // use listener listener.Close(); listener = null; 

Leia o excelente artigo de Jon Skeet sobre events. Não é um verdadeiro “memory leaks” no sentido clássico, mas mais uma referência que não foi desconectada. Por isso, lembre-se sempre de -= um manipulador de events que você += em um ponto anterior e você deve ser de ouro.

Não existem, estritamente falando, “vazamentos de memory” dentro do “sandbox” de um projeto .NET gerenciado; existem apenas referências mais longas do que o desenvolvedor acharia necessário. Fredrik tem o direito disso; Quando você anexa um manipulador a um evento, porque o manipulador geralmente é um método de instância (que requer a instância), a instância da class que contém o listener permanece na memory, desde que essa referência seja mantida. Se a instância do listener contiver referências a outras classs, por sua vez (por exemplo, referências anteriores a objects contidos), o heap poderá ficar muito grande por muito tempo após o ouvinte ter saído de todos os outros escopos.

Talvez alguém com um conhecimento um pouco mais esotérico de Delegate e MulticastDelegate possa lançar alguma luz sobre isso. Do jeito que eu vejo, um vazamento verdadeiro poderia ser possível se todos os itens a seguir fossem verdadeiros:

  • O ouvinte de evento requer resources externos / não gerenciados para serem liberados pela implementação de IDisposable, mas não
  • O delegado multicast do evento NÃO chama os methods Dispose () de seu método Finalize () substituído, e
  • A class que contém o evento não chama Dispose () em cada destino do delegado por meio de sua própria implementação IDisposable ou em Finalize ().

Eu nunca ouvi falar de nenhuma prática recomendada envolvendo chamar Dispose () em Delegate Targets, muito menos ouvintes de events, então eu só posso assumir que os desenvolvedores .NET sabiam o que estavam fazendo neste caso. Se isso for verdade, e o MulticastDelegate por trás de um evento tentar descartar adequadamente os ouvintes, tudo o que for necessário é a implementação adequada de IDisposable em uma class de escuta que exija descarte.