Impostazione di un oggetto su null vs Dispose ()


108

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

Comunque, qual è la differenza tra dire:

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

Oppure, facendo in modo che MyClass implementi IDisposable e un distruttore e chiamando Dispose ()?

Inoltre, se ho un blocco di codice con un'istruzione using (ad esempio sotto), se passo attraverso il codice ed esco dal blocco using, l'oggetto viene eliminato in quel momento o quando si verifica una raccolta dei rifiuti? Cosa succederebbe se chiamassi Dispose () nel blocco using comunque?

using (MyDisposableObj mydispobj = new MyDisposableObj())
{

}

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

Risposte:


210

È 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 usingun'istruzione, è semplicemente zucchero sintattico per un blocco try / finalmente in modo che Disposevenga chiamato anche se il codice nel corpo usingdell'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 IDisposableogni volta che il tuo tipo "possiede" una risorsa non gestita, direttamente (solitamente tramite an IntPtr) o indirettamente (es. Tramite a Stream, a SqlConnectionecc).

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 FileStreamma dimentichi di chiamare Disposeo 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 FileStreamcome 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 FileStreamavere 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 aSafeHandleappena 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 Disposeper 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 :)


Ri "L'unica volta in cui può valere la pena impostare una variabile locale su null" - forse anche alcuni degli scenari di "cattura" più spinosi (acquisizioni multiple della stessa variabile) - ma potrebbe non valere la pena complicare il post! +1 ...
Marc Gravell

@ Marc: È vero, non avevo nemmeno pensato alle variabili acquisite. Hmm. Sì, penso che lo lascerò stare;)
Jon Skeet

potresti per favore dire che cosa succederà quando imposterai "foo = null" nel tuo frammento di codice sopra? Per quanto ne so, quella riga cancella solo il valore di una variabile che punta all'oggetto foo nell'heap gestito? quindi la domanda è cosa succederà all'oggetto foo lì? non dovremmo chiamarlo smaltire?
odiseh

@odiseh: se l'oggetto fosse usa e getta, allora sì, dovresti smaltirlo. Quella sezione della risposta riguardava solo la raccolta dei rifiuti, che è completamente separata.
Jon Skeet

1
Stavo cercando un chiarimento su alcuni problemi IDisposable, quindi ho cercato su Google "IDisposable Skeet" e ho trovato questo. Grande! : D
Maciej Wozniak

22

Quando si elimina un oggetto, le risorse vengono liberate. Quando assegni null a una variabile, stai solo modificando un riferimento.

myclass = null;

Dopo aver eseguito questa operazione, l'oggetto a cui si riferiva myclass esiste ancora e continuerà finché il GC non riuscirà a ripulirlo. Se Dispose viene chiamato esplicitamente o si trova in un blocco using, tutte le risorse verranno liberate il prima possibile.


7
Si può non esiste ancora dopo aver eseguito questa linea - potrebbe essere stato garbage collection prima di quella linea. Il JIT è intelligente, rendendo linee come questa quasi sempre irrilevanti.
Jon Skeet,

6
L'impostazione su null potrebbe significare che le risorse detenute dall'oggetto non vengono mai liberate. Il GC non elimina, si finalizza solo, quindi se l'oggetto contiene direttamente risorse non gestite e il suo finalizzatore non elimina (o non ha un finalizzatore), tali risorse verranno perse. Qualcosa di cui essere consapevoli.
LukeH

6

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

Quando chiami Dispose (), è una chiamata al metodo sull'oggetto stesso. Qualunque cosa faccia il metodo Dispose, ora viene eseguita sull'oggetto. Ma questo non influisce sul tuo riferimento all'oggetto.

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

Ma ciò non accadrà immediatamente dopo aver impostato il riferimento su null, per due motivi. Primo, potrebbero esistere altri riferimenti, quindi non verrà ancora raccolto il garbage collector, e secondo, anche se quello era l'ultimo riferimento, quindi ora è pronto per essere garbage collector, non accadrà nulla finché il garbage collector non decide di eliminare 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 eliminato in modo sicuro in seguito, ma alla fine, non c'è nulla di magico in Dispose, è solo un metodo di classe.


Penso che questa risposta complimenti o sia un dettaglio alla risposta di "ricorsivo".
dance2die
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.