Ci sono due modi (che io sappia) per causare una perdita di memoria non intenzionale in C #:
IDisposable
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:
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.