In che modo gli eventi causano perdite di memoria in C # e in che modo i riferimenti deboli aiutano a mitigarli?

Ci sono due modi (che io sappia) per causare una perdita di memoria non intenzionale in C #:

  1. Non disporre di risorse che implementano IDisposable
  2. Riferire e de-referenziare gli eventi in modo errato.

Non capisco davvero il secondo punto. Se l’object di origine ha una durata maggiore rispetto al listener e il listener non ha più bisogno degli eventi quando non ci sono altri riferimenti ad esso, l’uso di eventi .NET normali causa una perdita di memoria: l’object di origine contiene oggetti listener in memoria che dovrebbe essere raccolta dei rifiuti

Puoi spiegare come gli eventi possono causare perdite di memoria con il codice in C # e come posso codificare per aggirarlo usando i riferimenti deboli e senza riferimenti deboli?

Quando un listener collega un listener di eventi a un evento, l’object di origine otterrà un riferimento all’object listener. Ciò significa che il garbage collector non può raccogliere il listener fino a quando il gestore di eventi non viene rimosso o l’object di origine viene raccolto.

Considera le seguenti classi:

 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 poi il seguente codice:

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

Anche se assegniamo null a listener , non sarà idoneo per la garbage collection, poiché newSource mantiene ancora un riferimento al gestore di eventi ( Listener.source_SomeEvent ). Per risolvere questo tipo di perdita, è importante separare sempre gli ascoltatori di eventi quando non sono più necessari.

Il precedente esempio è stato scritto per concentrarsi sul problema con la perdita. Per risolvere quel codice, il più semplice sarà forse lasciare che Listener mantenga un riferimento a Source , in modo che possa in seguito staccare il listener di eventi:

 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; } } } 

Quindi il codice chiamante può segnalare che è stato fatto usando l’object, che rimuoverà il riferimento che Source ha a’Listener`;

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

Leggi l’eccellente articolo di Jon Skeet sugli eventi. Non è una vera “perdita di memoria” nel senso classico, ma più di un riferimento tenuto che non è stato disconnesso. Quindi ricorda sempre a -= un gestore di eventi che += al punto precedente e dovresti essere d’oro.

Non ci sono, in senso stretto, “perdite di memoria” all’interno della “sandbox” di un progetto .NET gestito; ci sono solo riferimenti tenuti più a lungo di quanto lo sviluppatore ritenga necessario. Fredrik ha il diritto di farlo; quando si collega un gestore a un evento, poiché il gestore è di solito un metodo di istanza (che richiede l’istanza), l’istanza della class contenente il listener resta in memoria fino a quando viene mantenuto questo riferimento. Se l’istanza del listener contiene a sua volta riferimenti ad altre classi (ad es. Backreferences per contenere oggetti), l’heap può rimanere abbastanza grande a lungo dopo che il listener è uscito da tutti gli altri ambiti.

Forse qualcuno con una conoscenza un po ‘più esoterica di Delegate e MulticastDelegate può far luce su questo. Per come la vedo io, una vera perdita potrebbe essere ansible se tutte le seguenti fossero vere:

  • Il listener di eventi richiede che le risorse esterne / non gestite vengano rilasciate implementando IDisposable, oppure no, oppure
  • Il delegato multicast dell’evento NON chiama i metodi Dispose () dal metodo Finalize () sottoposto a override e
  • La class che contiene l’evento non chiama Dispose () su ogni Target del delegato attraverso la sua implementazione IDisposable, o in Finalize ().

Non ho mai sentito parlare di buone pratiche che coinvolgono la chiamata Dispose () sui destinatari delegati, e molto meno gli ascoltatori di eventi, quindi posso solo presumere che gli sviluppatori .NET sapessero cosa stavano facendo in questo caso. Se questo è vero, e il MulticastDelegate dietro un evento cerca di disporre correttamente degli ascoltatori, tutto ciò che è necessario è una corretta implementazione di IDisposable su una class di ascolto che richiede lo smaltimento.