Qual è la differenza tra l'utilizzo di IDisposable e un distruttore in C #?


101

Quando implementerei IDispose su una classe anziché su un distruttore? Ho letto questo articolo , ma ancora mi manca il punto.

La mia ipotesi è che se implemento IDispose su un oggetto, posso esplicitamente "distruggerlo" invece di aspettare che lo faccia il garbage collector. È corretto?

Ciò significa che dovrei sempre chiamare esplicitamente Dispose su un oggetto? Quali sono alcuni esempi comuni di questo?


5
In effetti, dovresti chiamare Dispose su ogni oggetto usa e getta. Puoi farlo facilmente usando il usingcostrutto.
Luc Touraille

Ah, ha senso. Mi sono sempre chiesto perché l'istruzione "using" fosse utilizzata per i flussi di file. So che aveva qualcosa a che fare con l'ambito dell'oggetto, ma non l'ho contestualizzato con l'interfaccia IDisposable.
Jordan Parmer

5
Un punto importante da ricordare è che un finalizzatore non dovrebbe mai accedere ai membri gestiti di una classe, poiché tali membri potrebbero non essere più riferimenti validi.
Dan Bryant

Risposte:


126

Un finalizzatore (noto anche come distruttore) fa parte della garbage collection (GC) - è indeterminato quando (o anche se) ciò accade, poiché GC si verifica principalmente a causa della pressione della memoria (cioè necessita di più spazio). I finalizzatori vengono generalmente utilizzati solo per ripulire le risorse non gestite , poiché le risorse gestite avranno la propria raccolta / eliminazione.

Quindi IDisposableè usato per pulire deterministicamente gli oggetti, cioè adesso. Non raccoglie la memoria dell'oggetto (che appartiene ancora a GC), ma viene utilizzato ad esempio per chiudere file, connessioni al database, ecc.

Ci sono molti argomenti precedenti su questo:

Infine, tieni presente che non è raro che un IDisposableoggetto abbia anche un finalizzatore; in questo caso, di Dispose()solito chiama GC.SuppressFinalize(this), il che significa che GC non esegue il finalizzatore, semplicemente butta via la memoria (molto più economico). Il finalizzatore viene comunque eseguito se ti dimentichi Dispose()dell'oggetto.


Grazie! Questo ha perfettamente senso. Apprezzo molto l'ottima risposta.
Jordan Parmer

27
Una cosa in più da dire. Non aggiungere un finalizzatore alla tua classe a meno che tu non ne abbia davvero bisogno. Se aggiungi un finalizzatore (distruttore), il GC deve chiamarlo (anche un finalizzatore vuoto) e per chiamarlo l'oggetto sopravviverà sempre a un Garbage Collect di prima generazione. Ciò impedirà e rallenterà il GC. Questo è ciò che Marc dice di chiamare SuppressFinalize nel codice sopra
Kevin Jones

1
Quindi Finalize consiste nel rilasciare risorse non gestite. Ma Dispose potrebbe essere utilizzato per rilasciare risorse gestite e non gestite?
Dark_Knight

2
@ Scuro sì; perché 6 livelli più in basso nella catena delle
gestioni

1
@KevinJones Gli oggetti con un finalizzatore sono garantiti per sopravvivere alla generazione 0, non alla prima, giusto? L'ho letto in un libro intitolato .NET Performance.
David Klempfner

25

Il ruolo del Finalize()metodo è garantire che un oggetto .NET possa ripulire le risorse non gestite durante la garbage collection . Tuttavia, oggetti come connessioni al database o gestori di file dovrebbero essere rilasciati il ​​prima possibile, invece di fare affidamento sulla raccolta dei rifiuti. Per questo dovresti implementare l' IDisposableinterfaccia e rilasciare le tue risorse nel Dispose()metodo.


9

C'è un'ottima descrizione su MSDN :

L'utilizzo principale di questa interfaccia è il rilascio di risorse non gestite . Il Garbage Collector rilascia automaticamente la memoria allocata a un oggetto gestito quando tale oggetto non viene più utilizzato. Tuttavia, non è possibile prevedere quando avverrà la garbage collection . Inoltre, il garbage collector non è a conoscenza di risorse non gestite come gli handle di finestre o di file e flussi aperti .

Utilizzare il metodo Dispose di questa interfaccia per rilasciare in modo esplicito le risorse non gestite insieme al Garbage Collector. Il consumatore di un oggetto può chiamare questo metodo quando l'oggetto non è più necessario.


1
Uno dei principali punti deboli di questa descrizione è che MS fornisce esempi di risorse non gestite, ma da quello che ho visto non definisce mai il termine. Poiché gli oggetti gestiti sono generalmente utilizzabili solo all'interno del codice gestito, si potrebbe pensare che le cose usate nel codice non gestito siano risorse non gestite, ma non è proprio vero. Molto codice non gestito non utilizza alcuna risorsa e alcuni tipi di risorse non gestite come gli eventi esistono solo nell'universo del codice gestito.
supercat

1
Se un oggetto di breve durata sottoscrive un evento da un oggetto di lunga durata (ad es. Chiede di essere informato di eventuali modifiche che si verificano entro la durata di vita dell'oggetto di breve durata), tale evento dovrebbe essere considerato una risorsa non gestita, poiché il mancato annullare la sottoscrizione dell'evento causerebbe l'estensione della durata dell'oggetto di breve durata a quella dell'oggetto di lunga durata. Se molte migliaia o milioni di oggetti di breve durata si iscrissero a un evento ma venissero abbandonati senza annullare l'iscrizione, ciò potrebbe causare una perdita di memoria o CPU (poiché il tempo necessario per elaborare ogni sottoscrizione aumenterebbe).
supercat

1
Un altro scenario che coinvolge risorse non gestite all'interno del codice gestito sarebbe l'allocazione di oggetti dai pool. Soprattutto se il codice deve essere eseguito in .NET Micro Framework (il cui garbage collector è molto meno efficiente di quello sulle macchine desktop) può essere utile che il codice abbia ad esempio un array di strutture, ciascuna delle quali può essere contrassegnata come "usata" o "gratuito". Una richiesta di allocazione dovrebbe trovare una struttura attualmente contrassegnata come "libera", contrassegnarla come "usata" e restituirle un indice; una richiesta di rilascio dovrebbe contrassegnare una struttura come "libera". Se una richiesta di assegnazione restituisce, ad esempio, 23, allora ...
supercat

1
... se il codice non notifica mai al proprietario dell'array che non ha più bisogno dell'elemento # 23, lo slot dell'array non sarà mai utilizzabile da nessun altro codice. Tale allocazione manuale dagli slot degli array non viene utilizzata molto spesso nel codice desktop poiché il GC è piuttosto efficiente, ma nel codice in esecuzione su Micro Framework può fare un'enorme differenza.
supercat

8

L'unica cosa che dovrebbe essere in un distruttore C # è questa riga:

Dispose(False);

Questo è tutto. Nient'altro dovrebbe mai essere in quel metodo.


3
Questo è il design pattern proposto da Microsoft nella documentazione .NET, ma non usarlo quando il tuo oggetto non è IDisposabile. msdn.microsoft.com/en-us/library/fs2xkftw%28v=vs.110%29.aspx
Zbyl

1
Non riesco a pensare a nessun motivo per offrire una classe con un finalizzatore che non abbia anche un metodo Dispose.
Jonathan Allen

4

La tua domanda sull'opportunità o meno di chiamare sempre Disposeè di solito un acceso dibattito. Vedi questo blog per una prospettiva interessante da parte di persone rispettate nella comunità .NET.

Personalmente, penso che la posizione di Jeffrey Richter sia quella che chiama Dispose non è obbligatoria sia incredibilmente debole. Fornisce due esempi per giustificare la sua opinione.

Nel primo esempio, dice che chiamare Disposei controlli Windows Form è noioso e non necessario negli scenari tradizionali. Tuttavia, non menziona che in Disposerealtà viene chiamato automaticamente dai contenitori di controllo in quegli scenari tradizionali.

Nel secondo esempio, afferma che uno sviluppatore può presumere erroneamente che l'istanza da IAsyncResult.WaitHandledebba essere eliminata in modo aggressivo senza rendersi conto che la proprietà inizializza pigramente l'handle di attesa con conseguente riduzione delle prestazioni non necessaria. Ma il problema con questo esempio è che lo IAsyncResultstesso non aderisce alle linee guida pubblicate da Microsoft per la gestione degli IDisposableoggetti. Cioè, se una classe contiene un riferimento a un IDisposabletipo, la classe stessa dovrebbe implementare IDisposable. Se IAsyncResultseguisse quella regola, il suo Disposemetodo potrebbe prendere la decisione riguardo a quale dei suoi membri costituenti necessita di essere smaltito.

Quindi, a meno che qualcuno non abbia un argomento più convincente, rimarrò nel campo "chiama sempre Dispose" con la consapevolezza che ci saranno alcuni casi marginali che sorgono principalmente da scelte di progettazione sbagliate.


3

È piuttosto semplice in realtà. So che è stata data una risposta, ma riproverò ma cercherò di mantenerlo il più semplice possibile.

Un distruttore in genere non dovrebbe mai essere utilizzato. È solo run .net vuole che funzioni. Funzionerà solo dopo un ciclo di garbage collectoin. Potrebbe non essere mai effettivamente eseguito durante il ciclo di vita dell'applicazione. Per questo motivo, non dovresti mai inserire alcun codice in un distruttore che "deve" essere eseguito. Inoltre, non è possibile fare affidamento sull'esistenza di oggetti esistenti all'interno della classe quando viene eseguita (potrebbero essere già stati ripuliti poiché l'ordine in cui vengono eseguiti i distruttori non è garantito).

IDisposible dovrebbe essere utilizzato ogni volta che si dispone di un oggetto che crea risorse che necessitano di essere ripulite (ad esempio, handle di file e grafici). In effetti, molti sostengono che tutto ciò che si inserisce in un distruttore dovrebbe essere messo in IDisposable per i motivi sopra elencati.

La maggior parte delle classi chiamerà dispose quando viene eseguito il finalizzatore, ma questo è semplicemente lì come una guardia sicura e non dovrebbe mai essere invocato. Dovresti eliminare esplicitamente tutto ciò che implementa IDisposable quando hai finito. Se implementi IDisposable, dovresti chiamare dispose nel finalizer. Vedi http://msdn.microsoft.com/en-us/library/system.idisposable.aspx per un esempio.


No, il garbage collector non chiama mai Dispose (). E ' solo chiama il finalizzatore.
Marc Gravell

Risolto. Le classi dovrebbero chiamare dispose nel finalizer, ma non è necessario.
DaEagle

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.