2009-02-22 20 views
93

Sono affascinato dal modo in cui funzionano CLR e GC (sto lavorando per espandere le mie conoscenze su questo leggendo CLR tramite C#, i libri/post di Jon Skeet e altro).Impostazione di un oggetto su nullo vs Dispose()

In ogni caso, qual è la differenza tra il dire:

MyClass myclass = new MyClass(); 
myclass = null; 

Oppure, facendo MyClass implementano IDisposable e un distruttore e chiamando Dispose()?

Inoltre, se si dispone di un blocco di codice con un'istruzione using (ad esempio sotto), se passo il codice e esco dal blocco using, l'oggetto viene eliminato o quando si verifica un garbage collection? Che cosa accadrebbe se chiamassi Dispose() nel blocco using in qualunque momento?

using (MyDisposableObj mydispobj = new MyDisposableObj()) 
{ 

} 

Le classi di flusso (ad es. BinaryWriter) hanno un metodo Finalize? Perché dovrei usarlo?

risposta

185

È 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' :)

+0

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 ... –

+0

@Marc: È vero: non avevo nemmeno pensato alle variabili catturate. Hmm. Sì, penso che lascerò tutto da solo;) –

+9

Wow. Capisco finalmente la fonte del culto degli Skeetisti. Questo post è fantastico! – JohnFx

19

Quando si smaltisce un oggetto, le risorse vengono liberate. Quando assegni null a una variabile, stai semplicemente cambiando un riferimento.

myclass = null; 

Dopo aver eseguito questo, l'myclass oggetto si riferiva al esiste ancora, e continuerà a fino a quando il GC ottiene intorno a pulirlo. Se Dispose viene chiamato esplicitamente, o è in un blocco using, tutte le risorse saranno liberate il prima possibile.

+7

E * potrebbe * non esiste ancora dopo aver eseguito questa linea - potrebbe essere stato garbage collection * prima * quella linea. Il JIT è intelligente, rendendo le linee come queste quasi sempre irrilevanti. –

+6

L'impostazione su null potrebbe significare che le risorse contenute nell'oggetto non sono * mai * liberate. Il GC non dispone, viene solo finalizzato, quindi se l'oggetto detiene direttamente risorse non gestite e il suo finalizzatore non dispone (o non ha un finalizzatore), quelle risorse si diffonderanno. Qualcosa di cui essere a conoscenza. – LukeH

4

Le due operazioni non hanno molto a che fare l'una con l'altra. Quando si imposta un riferimento su null, lo fa semplicemente. Di per sé non influisce sulla classe a cui è stato fatto riferimento. La tua variabile semplicemente non punta più sull'oggetto a cui era abituata, ma l'oggetto stesso rimane invariato.

Quando si chiama Dispose(), si tratta di una chiamata di metodo sull'oggetto stesso. Qualunque sia il metodo Dispose, ora viene eseguito sull'oggetto. Ma questo non influenza il tuo riferimento all'oggetto.

L'unica area di sovrapposizione è che quando non ci sono più riferimenti a un oggetto, che verrà alla fine ottenere garbage collection. E se la classe implementa l'interfaccia IDisposable, allora Dispose() verrà chiamato sull'oggetto prima che venga raccolto.

Ma ciò non avverrà immediatamente dopo aver impostato il riferimento su null, per due motivi. In primo luogo, potrebbero esistere altri riferimenti, quindi non verrà ancora raccolta la garbage collection, e in secondo luogo, anche se era l'ultimo riferimento, quindi è pronto per essere raccolto dalla garbage collection, non succederà nulla fino a quando il garbage collector non decide di cancella l'oggetto.

Chiamare Dispose() su un oggetto non "uccide" l'oggetto in alcun modo. È comunemente usato per pulire in modo che l'oggetto possa essere cancellato in modo sicuro in seguito, ma alla fine, non c'è nulla di magico in Dispose, è solo un metodo di classe.

+0

Penso che questa risposta sia complice o sia un dettaglio della risposta di "ricorsiva". – Sung

Problemi correlati