Il Garbage Collector chiama IDisposable.Dispose per me?

Il modello IDisposable .NET implica che se si scrive un finalizzatore e si implementa IDisposable, il proprio finalizzatore deve chiamare esplicitamente Dispose. Questo è logico, ed è quello che ho sempre fatto nelle rare situazioni in cui è giustificato un finalizzatore.

Tuttavia, cosa succede se faccio solo questo:

class Foo : IDisposable { public void Dispose(){ CloseSomeHandle(); } } 

e non implementare un finalizzatore o altro. Il framework chiamerà il metodo Dispose per me?

Sì, mi rendo conto che questo sembra stupido, e tutta la logica implica che non lo farà, ma ho sempre avuto 2 cose in fondo alla mia testa che mi hanno reso insicuro.

  1. Qualcuno qualche anno fa una volta mi disse che avrebbe effettivamente fatto questo, e quella persona aveva una solida esperienza di “conoscere le loro cose”.

  2. Il compilatore / framework fa altre cose “magiche” a seconda di quali interfacce implementate (es: foreach, metodi di estensione, serializzazione basata su attributi, ecc.), Quindi ha senso che anche questo possa essere “magico”.

Mentre ho letto molte cose a riguardo, e ci sono state molte cose implicite, non sono mai stato in grado di trovare una risposta definitiva a Sì o No a questa domanda.

Il .Net Garbage Collector chiama il metodo Object.Finalize di un object sulla garbage collection. Di default questo non fa nulla e deve essere sovrascritto se si desidera liberare risorse aggiuntive.

Smaltire NON viene automaticamente chiamato e deve essere chiamato esplicitamente se le risorse devono essere rilasciate, come all’interno di un blocco ‘using’ o ‘try finally’

vedi http://msdn.microsoft.com/en-us/library/system.object.finalize.aspx per maggiori informazioni

Voglio sottolineare il punto di Brian nel suo commento, perché è importante.

I finalizzatori non sono distruttori deterministici come in C ++. Come altri hanno sottolineato, non vi è alcuna garanzia su quando sarà chiamato, e in effetti se si dispone di memoria sufficiente, se sarà mai chiamato.

Ma la cosa brutta dei finalizzatori è che, come ha detto Brian, fa sì che il tuo object sopravviva a una garbage collection. Questo può essere cattivo. Perché?

Come puoi o non sai, il GC è diviso in generazioni: Gen 0, 1 e 2, più l’heap di oggetti di grandi dimensioni. Dividi è un termine approssimativo: ottieni un blocco di memoria, ma ci sono dei puntatori su dove gli oggetti Gen 0 iniziano e finiscono.

Il processo di pensiero è che probabilmente utilizzerai molti oggetti che avranno vita breve. Quindi quelli dovrebbero essere facili e veloci per il GC per arrivare a – Gen 0 oggetti. Quindi, quando c’è la pressione della memoria, la prima cosa che fa è una collezione Gen 0.

Ora, se questo non risolve abbastanza pressione, allora torna indietro e fa un Gen 1 sweep (rifare Gen 0), e poi se non è ancora abbastanza, fa un Gen 2 sweep (rifare Gen 1 e Gen 0). Pertanto, la pulizia di oggetti di lunga durata può richiedere un po ‘di tempo ed essere piuttosto costosa (poiché i thread possono essere sospesi durante l’operazione).

Ciò significa che se fai qualcosa di simile:

 ~MyClass() { } 

Il tuo object, non importa cosa, vivrà fino alla Generazione 2. Questo perché il GC non ha modo di chiamare il finalizzatore durante la garbage collection. Quindi gli oggetti che devono essere finalizzati vengono spostati in una coda speciale per essere eliminati da un thread diverso (il thread del finalizzatore – che se si uccide fa accadere tutti i tipi di cose brutte). Ciò significa che i tuoi oggetti rimangono in sospeso più a lungo e potenzialmente impongono ulteriori raccolte di dati inutili.

Quindi, tutto questo è solo per portare a casa il punto che si desidera utilizzare IDisposable per ripulire le risorse quando ansible e seriamente cercare di trovare modi per utilizzare il finalizzatore. È nell’interesse della tua applicazione.

C’è già una bella discussione qui, e sono un po ‘in ritardo per la festa, ma volevo aggiungere qualche punto io stesso.

  • Il Garbage Collecter non eseguirà mai direttamente un metodo Dispose per te.
  • Il GC eseguirà i finalizzatori quando ne ha voglia.
  • Uno schema comune che viene utilizzato per gli oggetti che hanno un finalizzatore è quello di richiamare un metodo che è per convenzione definito come Dispose (booolinging) che passa false per indicare che la chiamata è stata effettuata a causa della finalizzazione piuttosto che una chiamata esplicita Dispose.
  • Questo perché non è sicuro fare ipotesi su altri oggetti gestiti durante la finalizzazione di un object (potrebbero essere già stati finalizzati).
 class SomeObject : IDisposable { IntPtr _SomeNativeHandle; FileStream _SomeFileStream; // Something useful here ~ SomeObject() { Dispose(false); } public void Dispose() { Dispose(true); } protected virtual void Dispose(bool disposing) { if(disposing) { GC.SuppressFinalize(this); //Because the object was explicitly disposed, there will be no need to //run the finalizer. Suppressing it reduces pressure on the GC //The managed reference to an IDisposable is disposed only if the _SomeFileStream.Dispose(); } //Regardless, clean up the native handle ourselves. Because it is simple a member // of the current instance, the GC can't have done anything to it, // and this is the onlyplace to safely clean up if(IntPtr.Zero != _SomeNativeHandle) { NativeMethods.CloseHandle(_SomeNativeHandle); _SomeNativeHandle = IntPtr.Zero; } } } 

Questa è la versione semplice, ma ci sono molte sfumature che possono incitarti su questo modello.

  • Il contratto per IDisposable.Dispose indica che deve essere sicuro chiamare più volte (chiamare Dispose su un object che era già disponibile non dovrebbe fare nulla)
  • Può diventare molto complicato gestire correttamente una gerarchia di ereditarietà di oggetti usa e getta, specialmente se diversi livelli introducono nuove risorse eliminabili e non gestite. Nello schema sopra Dispose (bool) è virtuale per consentirne l’override in modo che possa essere gestito, ma trovo che sia sobject a errori.

A mio parere, è molto meglio evitare di avere qualsiasi tipo che contenga direttamente sia riferimenti monouso che risorse native che potrebbero richiedere la finalizzazione. SafeHandles fornisce un modo molto semplice per fare ciò incapsulando le risorse native in monouso che internamente forniscono la loro finalizzazione (insieme con una serie di altri vantaggi come la rimozione della finestra durante P / Invoke dove un handle nativo potrebbe essere perso a causa di un’eccezione asincrona) .

La semplice definizione di SafeHandle rende questo Trivial:

 private class SomeSafeHandle : SafeHandleZeroOrMinusOneIsInvalid { public SomeSafeHandle() : base(true) { } protected override bool ReleaseHandle() { return NativeMethods.CloseHandle(handle); } } 

Ti permette di semplificare il tipo di contenuto per:

 class SomeObject : IDisposable { SomeSafeHandle _SomeSafeHandle; FileStream _SomeFileStream; // Something useful here public virtual void Dispose() { _SomeSafeHandle.Dispose(); _SomeFileStream.Dispose(); } } 

Io non la penso così Hai il controllo su quando viene chiamato Dispose, il che significa che potresti teoricamente scrivere codice di smaltimento che faccia supposizioni riguardo (per esempio) all’esistenza di altri oggetti. Non hai il controllo su quando viene chiamato il finalizzatore, quindi sarebbe incerto se il finalizzatore chiamasse automaticamente Dispose per tuo conto.


EDIT: sono andato via e testato, solo per essere sicuro:

 class Program { static void Main(string[] args) { Fred f = new Fred(); f = null; GC.Collect(); GC.WaitForPendingFinalizers(); Console.WriteLine("Fred's gone, and he's not coming back..."); Console.ReadLine(); } } class Fred : IDisposable { ~Fred() { Console.WriteLine("Being finalized"); } void IDisposable.Dispose() { Console.WriteLine("Being Disposed"); } } 

Non nel caso in cui descrivi, ma il GC chiamerà il Finalizer per te, se ne hai uno.

PERÒ. La prossima garbage collection, invece di essere raccolta, l’object andrà nella finalizzazione, tutto verrà raccolto, quindi chiamato finalizzatore. La prossima raccolta dopo sarà liberata.

A seconda della pressione della memoria della tua app, potresti non avere un gc per quella generazione di oggetti per un po ‘. Quindi, nel caso di un stream di file o una connessione db, potrebbe essere necessario attendere un po ‘affinché la risorsa non gestita venga liberata per un po’ durante la chiamata del finalizzatore, causando alcuni problemi.

Il GC non chiamerà dispose. Può chiamare il tuo finalizzatore, ma anche questo non è garantito in tutte le circostanze.

Vedi questo articolo per una discussione sul modo migliore per gestirlo.

No, non è chiamato.

Ma questo rende facile non dimenticare di disporre i tuoi oggetti. Basta usare la parola chiave using .

Ho fatto il seguente test per questo:

 class Program { static void Main(string[] args) { Foo foo = new Foo(); foo = null; Console.WriteLine("foo is null"); GC.Collect(); Console.WriteLine("GC Called"); Console.ReadLine(); } } class Foo : IDisposable { public void Dispose() { Console.WriteLine("Disposed!"); } 

La documentazione su IDisposable fornisce una spiegazione abbastanza chiara e dettagliata del comportamento, oltre al codice di esempio. Il GC NON chiamerà il metodo Dispose() sull’interfaccia, ma chiamerà il finalizzatore per il tuo object.

Lo schema IDisposable è stato creato principalmente per essere chiamato dallo sviluppatore, se si dispone di un object che implementa IDispose, lo sviluppatore deve implementare la parola chiave using nel contesto dell’object o chiamare direttamente il metodo Dispose.

Il fail safe per il pattern è implementare il finalizzatore chiamando il metodo Dispose (). Se non lo fai, potresti creare delle perdite di memoria, ad es .: se crei un wrapper COM e non chiami mai System.Runtime.Interop.Marshall.ReleaseComObject (comObject) (che verrebbe inserito nel metodo Dispose).

Non c’è magia nel clr per chiamare automaticamente i metodi di Dispose oltre a tenere traccia degli oggetti che contengono i finalizzatori e archiviarli nella tabella Finalizer dal GC e chiamarli quando un po ‘di euristica di pulizia avvia il GC.