È importante separare lo smaltimento dalla raccolta dei rifiuti. Sono cose completamente separate, con un punto in comune al quale arriverò tra un minuto.
Dispose
, raccolta dei rifiuti e la messa a punto
Quando si scrive un comunicato using
, è lo zucchero semplicemente sintattico per un try/finally blocco in modo che Dispose
è chiamato anche se il codice nel corpo della dichiarazione using
getta un'eccezione. Lo non corrisponde a significa che l'oggetto è garbage collection alla fine del blocco.
Lo smaltimento è circa risorse non gestite (risorse non di memoria). Questi potrebbero essere maniglie dell'interfaccia utente, connessioni di rete, handle di file ecc. Si tratta di risorse limitate, quindi in genere si desidera rilasciarli il prima possibile. Dovresti implementare IDisposable
ogni volta che il tuo tipo "possiede" una risorsa non gestita, direttamente (di solito tramite uno IntPtr
) o indirettamente (ad esempio tramite uno Stream
, uno SqlConnection
ecc.).
La stessa raccolta di dati inutili riguarda solo la memoria, con una piccola svolta. Il garbage collector è in grado di trovare oggetti che non possono più essere referenziati e liberarli. Tuttavia, non ricerca sempre i dati inutili, ma solo quando rileva che è necessario (ad esempio se una "generazione" di heap esaurisce la memoria).
La svolta è finalizzazione. Il garbage collector mantiene una lista di oggetti che non sono più raggiungibili, ma che hanno un finalizzatore (scritto come ~Foo()
in C#, in qualche modo confuso - non sono nulla come i distruttori del 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 vengono quasi sempre utilizzati per ripulire le risorse nel caso in cui l'utente del tipo abbia dimenticato di smaltirlo in modo ordinato. Quindi se apri un FileStream
ma dimentichi di chiamare Dispose
o Close
, il finalizzatore sarà alla fine rilasciare l'handle di file sottostante per te. In un programma ben scritto, i finalizzatori non dovrebbero quasi mai sparare secondo me.
Impostazione di una variabile null
Un piccolo punto su come impostare una variabile per null
- questo non è quasi mai necessario per il bene di garbage collection. A volte potresti volerlo fare se è una variabile membro, sebbene nella mia esperienza sia raro che la "parte" di un oggetto non sia più necessaria. Quando si tratta di una variabile locale, il JIT di solito è abbastanza intelligente (in modalità di rilascio) per sapere quando non si utilizzerà nuovamente 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 si può valere impostare una variabile locale per null
è quando sei in un ciclo, e alcuni rami del ciclo è necessario utilizzare la variabile, ma si sai ho 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 IDisposable/finalizzatori
Quindi, se i vostri propri tipi implementare finalizzatori? Quasi certamente no. Se solo indirettamente,, mantieni le risorse non gestite (ad es.hai uno FileStream
come variabile membro), quindi aggiungere il tuo finalizzatore non ti aiuterà: lo stream sarà quasi sicuramente idoneo per la garbage collection quando l'oggetto è, quindi puoi semplicemente contare su FileStream
con un finalizzatore (se necessario - potrebbe riferirsi a qualcos'altro, ecc.) Se vuoi tenere una risorsa non gestita "quasi" direttamente, SafeHandle
è tuo amico - ci vuole un po 'di tempo per andare avanti, ma significa che lo avrai almostnever need to write a finalizer again. In genere, è necessario un finalizzatore solo se si ha un handle veramente diretto su una risorsa (uno IntPtr
) e si dovrebbe cercare di passare a SafeHandle
non appena possibile. (Ci sono due link lì - leggere entrambi, idealmente.)
Joe Duffy ha uno very long set of guidelines around finalizers and IDisposable (co-scritto con un sacco di gente intelligente) che vale la pena leggere. Vale la pena essere consapevoli del fatto che se si sigillano le lezioni, la vita diventa molto più semplice: il modello di sovrascrivere Dispose
per chiamare un nuovo metodo virtuale Dispose(bool)
è rilevante solo quando la classe è progettata per l'ereditarietà.
questo è stato un po 'di una passeggiata, ma vi prego di chiedere chiarimenti in cui vuoi un po' :)
Re "L'unica volta in cui può valere la pena impostare una variabile locale su null" - forse anche alcuni degli scenari di "cattura" più spinosi (più acquisizioni della stessa variabile) - ma potrebbe non valere la pena complicare il post! +1 ... –
@Marc: È vero: non avevo nemmeno pensato alle variabili catturate. Hmm. Sì, penso che lascerò tutto da solo;) –
Wow. Capisco finalmente la fonte del culto degli Skeetisti. Questo post è fantastico! – JohnFx