È importante separare lo smaltimento dalla raccolta dei rifiuti. Sono cose completamente separate, con un punto in comune a cui arriverò tra un minuto.
Dispose
, garbage collection e finalizzazione
Quando scrivi using
un'istruzione, è semplicemente zucchero sintattico per un blocco try / finalmente in modo che Dispose
venga chiamato anche se il codice nel corpo using
dell'istruzione genera un'eccezione. Ciò non significa che l'oggetto sia stato raccolto dalla spazzatura alla fine del blocco.
Lo smaltimento riguarda le risorse non gestite ( risorse non di memoria). Questi potrebbero essere gli handle dell'interfaccia utente, le connessioni di rete, gli handle di file ecc. Queste sono risorse limitate, quindi in genere si desidera rilasciarle il prima possibile. Dovresti implementare IDisposable
ogni volta che il tuo tipo "possiede" una risorsa non gestita, direttamente (solitamente tramite an IntPtr
) o indirettamente (es. Tramite a Stream
, a SqlConnection
ecc).
La raccolta dei rifiuti in sé riguarda solo la memoria, con una piccola svolta. Il garbage collector è in grado di trovare oggetti a cui non è più possibile fare riferimento e di liberarli. Tuttavia, non cerca sempre spazzatura, solo quando rileva che è necessario (ad esempio, se una "generazione" dell'heap esaurisce la memoria).
La svolta è la finalizzazione . Il garbage collector mantiene un elenco di oggetti che non sono più raggiungibili, ma che hanno un finalizzatore (scritto come ~Foo()
in C #, in modo un po 'confuso - non hanno niente a che fare con i distruttori C ++). Esegue i finalizzatori su questi oggetti, nel caso in cui debbano eseguire una pulizia extra prima che la loro memoria venga liberata.
I finalizzatori sono quasi sempre utilizzati per ripulire le risorse nel caso in cui l'utente del tipo si sia dimenticato di smaltirle in modo ordinato. Quindi, se apri un FileStream
ma dimentichi di chiamare Dispose
o Close
, il finalizzatore alla fine rilascerà l'handle del file sottostante per te. In un programma ben scritto, i finalizzatori non dovrebbero quasi mai essere attivati secondo me.
Impostazione di una variabile su null
Un piccolo punto sull'impostazione di una variabile su null
: questo non è quasi mai richiesto per motivi di garbage collection. A volte potresti volerlo fare se si tratta di una variabile membro, sebbene nella mia esperienza sia raro che "parte" di un oggetto non sia più necessaria. Quando è una variabile locale, il JIT è generalmente abbastanza intelligente (in modalità di rilascio) da sapere quando non utilizzerai più un riferimento. Per esempio:
StringBuilder sb = new StringBuilder();
sb.Append("Foo");
string x = sb.ToString();
// The string and StringBuilder are already eligible
// for garbage collection here!
int y = 10;
DoSomething(y);
// These aren't helping at all!
x = null;
sb = null;
// Assume that x and sb aren't used here
L'unica volta in cui può valere la pena impostare una variabile locale null
è quando sei in un ciclo, e alcuni rami del ciclo devono usare la variabile ma sai di aver raggiunto un punto in cui non lo fai. Per esempio:
SomeObject foo = new SomeObject();
for (int i=0; i < 100000; i++)
{
if (i == 5)
{
foo.DoSomething();
// We're not going to need it again, but the JIT
// wouldn't spot that
foo = null;
}
else
{
// Some other code
}
}
Implementazione di IDisposable / finalizers
Quindi, i tuoi tipi dovrebbero implementare i finalizzatori? Quasi certamente no. Se detieni solo indirettamente risorse non gestite (ad esempio hai un FileStream
come variabile membro), aggiungere il tuo finalizzatore non sarà di aiuto: lo stream sarà quasi certamente idoneo per la raccolta dei rifiuti quando il tuo oggetto è, quindi puoi semplicemente fare affidamento su FileStream
avere un finalizzatore (se necessario, potrebbe riferirsi a qualcos'altro, ecc.). Se vuoi tenere una risorsa non gestita "quasi" direttamente, SafeHandle
è il tuo amico: ci vuole un po 'di tempo per andare avanti, ma significa che non avrai quasi mai bisogno di scrivere di nuovo un finalizzatore . Di solito dovresti aver bisogno di un finalizzatore solo se hai un handle davvero diretto su una risorsa (an IntPtr
) e dovresti cercare di passare aSafeHandle
appena puoi. (Ci sono due collegamenti lì: leggi entrambi, idealmente.)
Joe Duffy ha una serie molto lunga di linee guida sui finalizzatori e IDisposable (scritte in collaborazione con molte persone intelligenti) che vale la pena leggere. Vale la pena essere consapevoli del fatto che sigillare le classi rende la vita molto più semplice: il modello di sovrascrittura Dispose
per chiamare un nuovo Dispose(bool)
metodo virtuale , ecc. È rilevante solo quando la classe è progettata per l'ereditarietà.
Questo è stato un po 'un divagare, ma per favore chiedi chiarimenti dove vorresti alcuni :)