Risposte:
Altri hanno già coperto la differenza tra Dispose
e Finalize
(tra l'altro il Finalize
metodo è ancora chiamato distruttore nelle specifiche del linguaggio), quindi aggiungerò solo un po 'di scenari in cui il Finalize
metodo è utile.
Alcuni tipi incapsulano le risorse usa e getta in un modo in cui è facile da usare e le smaltiscono in una sola azione. L'uso generale è spesso così: apri, leggi o scrivi, chiudi (Disponi). Si adatta molto bene al using
costrutto.
Altri sono un po 'più difficili. WaitEventHandles
ad esempio, non vengono utilizzati in questo modo in quanto vengono utilizzati per segnalare da un thread all'altro. La domanda allora diventa chi dovrebbe fare appello Dispose
a questi? Come tipi di protezione come questi implementano un Finalize
metodo, che assicura che le risorse vengano eliminate quando all'applicazione non viene più fatto riferimento.
Finalize
può essere giustificata è quando ci sono un certo numero di oggetti che sono interessati ad avere una risorsa mantenuta in vita, ma non c'è modo in cui un oggetto che cessa di essere interessato alla risorsa possa scoprire se è il l'ultimo. In tal caso, di Finalize
solito sparerà solo quando nessuno è interessato all'oggetto. Il tempismo lento di Finalize
è orribile per risorse non fungibili come file e blocchi, ma può essere giusto per risorse fungibili.
Il metodo finalizzatore viene chiamato quando il tuo oggetto viene garbage collection e non hai alcuna garanzia quando ciò accadrà (puoi forzarlo, ma danneggerà le prestazioni).
Il Dispose
metodo d'altra parte è destinato a essere chiamato dal codice che ha creato la classe in modo da poter pulire e liberare tutte le risorse che avete acquisito (dati non gestiti, le connessioni al database, file handle, ecc) il momento il codice è fatto con il tuo oggetto.
La pratica standard è implementare IDisposable
e in Dispose
modo che tu possa usare il tuo oggetto in una using
dichiarazione. Come using(var foo = new MyObject()) { }
. E nel tuo finalizzatore, chiami Dispose
, nel caso in cui il codice chiamante abbia dimenticato di eliminarti.
Finalize è il metodo backstop, chiamato dal garbage collector quando recupera un oggetto. Dispose è il metodo di "pulizia deterministica", chiamato dalle applicazioni per rilasciare preziose risorse native (handle di finestre, connessioni al database, ecc.) Quando non sono più necessarie, piuttosto che lasciarle trattenute indefinitamente fino a quando il GC si avvicina all'oggetto.
Come utente di un oggetto, usi sempre Dispose. Finalize è per il GC.
Come implementatore di una classe, se si possiedono risorse gestite che devono essere eliminate, si implementa Dispose. Se si detengono risorse native, si implementano sia Dispose che Finalize ed entrambi chiamano un metodo comune che rilascia le risorse native. Questi idiomi sono in genere combinati attraverso un metodo Dispose privato (bool disposing), che Dispose chiama con true e Finalizza le chiamate con false. Questo metodo libera sempre le risorse native, quindi controlla il parametro disposing e, se è vero, elimina le risorse gestite e chiama GC.SuppressFinalize.
Dispose
è buono e implementarlo correttamente è generalmente facile. Finalize
è malvagio, e attuarlo correttamente è generalmente difficile. Tra le altre cose, poiché il GC garantirà che l'identità di nessun oggetto venga mai "riciclata" fintanto che esiste un riferimento a quell'oggetto, è facile ripulire un mucchio di Disposable
oggetti, alcuni dei quali potrebbero essere già stati ripuliti, è nessun problema; qualsiasi riferimento a un oggetto su cui Dispose
è già stato chiamato rimarrà un riferimento a un oggetto su cui Dispose
è già stato richiamato.
Fred
possiede l'handle di file n. 42 e lo chiude, il sistema potrebbe associare lo stesso numero a un handle di file fornito a un'altra entità. In tal caso, l'handle del file n. 42 non si riferirebbe al file chiuso di Fred, ma al file che era in uso attivo da quell'altra entità; per Fred
provare a chiudere nuovamente la maniglia # 42 sarebbe disastroso. Cercare di tenere traccia in modo affidabile del 100% se un oggetto non gestito è stato ancora rilasciato è praticabile. Cercare di tenere traccia di più oggetti è molto più difficile.
Finalizza
protected
, non public
o private
in modo che il metodo non può essere chiamato dal codice dell'applicazione direttamente e allo stesso tempo, si può effettuare una chiamata albase.Finalize
metodoSmaltire
IDisposable
su ogni tipo che ha un finalizzatoreDispose
metodo. In altre parole, evitare di usare un oggetto dopo che il Dispose
metodo è stato chiamato su di esso.Dispose
tutti i IDisposable
tipi una volta che hai finito con loroDispose
di essere chiamato più volte senza generare errori.Dispose
metodo utilizzando il GC.SuppressFinalize
metodoDispose
metodiDispose / modello finalizzato
Dispose
e Finalize
quando si lavora con risorse non gestite. L' Finalize
implementazione verrebbe eseguita e le risorse verrebbero comunque rilasciate quando l'oggetto viene garbage collection anche se uno sviluppatore trascura di chiamare il Dispose
metodo esplicitamente.Finalize
metodo e nel Dispose
metodo. Inoltre, chiama il Dispose
metodo per tutti gli oggetti .NET che hai come componenti all'interno di quella classe (con risorse non gestite come loro membro) dal Dispose
metodo.Finalize viene chiamato dal GC quando questo oggetto non è più in uso.
Dispose è solo un metodo normale che l'utente di questa classe può chiamare per rilasciare qualsiasi risorsa.
Se l'utente ha dimenticato di chiamare Dispose e se la classe ha Finalize implementato, GC si assicurerà che venga chiamato.
Ci sono alcune chiavi sul libro MCSD Certification Toolkit (esame 70-483) pag 193:
destructor ≈ (è quasi uguale a)base.Finalize()
, Il distruttore viene convertito in una versione di override del metodo Finalize che esegue il codice del distruttore e quindi chiama il metodo Finalize della classe base. Quindi è totalmente non deterministico che non puoi sapere quando verrà chiamato perché dipende da GC.
Se una classe non contiene risorse gestite e risorse non gestite , non dovrebbe implementare IDisposable
o avere un distruttore.
Se la classe ha gestito solo risorse , dovrebbe implementarsi IDisposable
ma non dovrebbe avere un distruttore. (Quando il distruttore viene eseguito, non si può essere sicuri che esistano ancora oggetti gestiti, quindi non è possibile chiamare Dispose()
comunque i loro metodi.)
Se la classe ha solo risorse non gestite , deve implementare IDisposable
e necessita di un distruttore nel caso in cui il programma non chiami Dispose()
.
Dispose()
il metodo deve essere sicuro per essere eseguito più di una volta. Puoi farlo utilizzando una variabile per tenere traccia del fatto che sia stata eseguita in precedenza.
Dispose()
dovrebbe liberare risorse gestite e non gestite .
Il distruttore dovrebbe liberare solo risorse non gestite . Quando il distruttore viene eseguito, non si può essere sicuri che esistano ancora oggetti gestiti, quindi non è possibile chiamare comunque i loro metodi Dispose. Ciò si ottiene utilizzando il protected void Dispose(bool disposing)
modello canonico , in cui solo le risorse gestite vengono liberate (eliminate) quando disposing == true
.
Dopo aver liberato le risorse, Dispose()
dovrebbe chiamareGC.SuppressFinalize
, in modo che l'oggetto possa saltare la coda di finalizzazione.
Un esempio di un'implementazione per una classe con risorse non gestite e gestite:
using System;
class DisposableClass : IDisposable
{
// A name to keep track of the object.
public string Name = "";
// Free managed and unmanaged resources.
public void Dispose()
{
FreeResources(true);
// We don't need the destructor because
// our resources are already freed.
GC.SuppressFinalize(this);
}
// Destructor to clean up unmanaged resources
// but not managed resources.
~DisposableClass()
{
FreeResources(false);
}
// Keep track if whether resources are already freed.
private bool ResourcesAreFreed = false;
// Free resources.
private void FreeResources(bool freeManagedResources)
{
Console.WriteLine(Name + ": FreeResources");
if (!ResourcesAreFreed)
{
// Dispose of managed resources if appropriate.
if (freeManagedResources)
{
// Dispose of managed resources here.
Console.WriteLine(Name + ": Dispose of managed resources");
}
// Dispose of unmanaged resources here.
Console.WriteLine(Name + ": Dispose of unmanaged resources");
// Remember that we have disposed of resources.
ResourcesAreFreed = true;
}
}
}
Il 99% delle volte, non dovresti preoccuparti di nessuno dei due. :) Ma, se i tuoi oggetti contengono riferimenti a risorse non gestite (handle di finestre, handle di file, ad esempio), devi fornire un modo per far sì che l'oggetto gestito rilasci tali risorse. Finalize offre un controllo implicito sul rilascio di risorse. Viene chiamato dal Garbage Collector. Dispose è un modo per dare un controllo esplicito su un rilascio di risorse e può essere chiamato direttamente.
C'è molto di più da imparare sull'argomento della Garbage Collection , ma è un inizio.
Il finalizzatore è per la pulizia implicita: dovresti usarlo ogni volta che una classe gestisce risorse che devono assolutamente essere ripulite, altrimenti perderai handle / memoria ecc ...
L'implementazione corretta di un finalizzatore è notoriamente difficile e dovrebbe essere evitata laddove possibile - la SafeHandle
classe (disponibile in .Net v2.0 e successive) ora significa che molto raramente (se mai) è necessario implementare un finalizzatore più.
Il IDisposable
interfaccia è per la pulizia esplicita ed è molto più comunemente usata: è necessario utilizzarla per consentire agli utenti di rilasciare o pulire esplicitamente le risorse ogni volta che hanno finito di usare un oggetto.
Si noti che se si dispone di un finalizzatore, è necessario implementare anche l' IDisposable
interfaccia per consentire agli utenti di rilasciare esplicitamente tali risorse prima di quanto sarebbero se l'oggetto fosse garbage collection.
Vedi DG Update: Dispose, Finalization e Resource Management per quello che considero il migliore e più completo insieme di raccomandazioni su finalizzatori e IDisposable
.
Il riassunto è -
Inoltre, un'altra differenza è: nell'implementazione di Dispose (), è necessario rilasciare anche le risorse gestite , mentre ciò non dovrebbe essere fatto in Finalizer. Questo perché è molto probabile che le risorse gestite a cui fa riferimento l'oggetto siano già state ripulite prima che sia pronto per essere finalizzato.
Per una classe che utilizza risorse non gestite, la migliore pratica è definire sia il metodo Dispose () che il Finalizzatore - da utilizzare come fallback nel caso in cui uno sviluppatore dimentichi di eliminare esplicitamente l'oggetto. Entrambi possono utilizzare un metodo condiviso per ripulire le risorse gestite e non gestite: -
class ClassWithDisposeAndFinalize : IDisposable
{
// Used to determine if Dispose() has already been called, so that the finalizer
// knows if it needs to clean up unmanaged resources.
private bool disposed = false;
public void Dispose()
{
// Call our shared helper method.
// Specifying "true" signifies that the object user triggered the cleanup.
CleanUp(true);
// Now suppress finalization to make sure that the Finalize method
// doesn't attempt to clean up unmanaged resources.
GC.SuppressFinalize(this);
}
private void CleanUp(bool disposing)
{
// Be sure we have not already been disposed!
if (!this.disposed)
{
// If disposing equals true i.e. if disposed explicitly, dispose all
// managed resources.
if (disposing)
{
// Dispose managed resources.
}
// Clean up unmanaged resources here.
}
disposed = true;
}
// the below is called the destructor or Finalizer
~ClassWithDisposeAndFinalize()
{
// Call our shared helper method.
// Specifying "false" signifies that the GC triggered the cleanup.
CleanUp(false);
}
Il miglior esempio che conosco.
public abstract class DisposableType: IDisposable
{
bool disposed = false;
~DisposableType()
{
if (!disposed)
{
disposed = true;
Dispose(false);
}
}
public void Dispose()
{
if (!disposed)
{
disposed = true;
Dispose(true);
GC.SuppressFinalize(this);
}
}
public void Close()
{
Dispose();
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
// managed objects
}
// unmanaged objects and resources
}
}
Diff tra i metodi Finalize e Dispose in C #.
GC chiama il metodo finalize per recuperare le risorse non gestite (come operarion file, api windows, connessione di rete, connessione al database) ma il tempo non è fisso quando GC lo chiamerebbe. Viene chiamato implicitamente da GC, il che significa che non abbiamo un controllo di basso livello su di esso.
Metodo di eliminazione: abbiamo un controllo di basso livello su di esso come lo chiamiamo dal codice. possiamo recuperare le risorse non gestite ogni volta che riteniamo che non sia utilizzabile. Possiamo raggiungere questo obiettivo implementando il modello IDisposal.
Le istanze di classe spesso incapsulano il controllo su risorse che non sono gestite dal runtime, come handle di finestra (HWND), connessioni al database e così via. Pertanto, è necessario fornire un modo esplicito e implicito per liberare tali risorse. Fornire il controllo implicito implementando il metodo Finalize protetto su un oggetto (sintassi del distruttore in C # e Managed Extensions per C ++). Il garbage collector chiama questo metodo ad un certo punto dopo che non ci sono più riferimenti validi all'oggetto. In alcuni casi, potresti voler fornire ai programmatori che utilizzano un oggetto la possibilità di rilasciare esplicitamente queste risorse esterne prima che il garbage collector liberi l'oggetto. Se una risorsa esterna è scarsa o costosa, è possibile ottenere prestazioni migliori se il programmatore rilascia esplicitamente risorse quando non vengono più utilizzate. Per fornire un controllo esplicito, implementare il metodo Dispose fornito dall'interfaccia IDisposable. Il consumatore dell'oggetto dovrebbe chiamare questo metodo quando viene fatto usando l'oggetto. Dispose può essere chiamato anche se altri riferimenti all'oggetto sono vivi.
Si noti che anche quando si fornisce un controllo esplicito tramite Dispose, è necessario fornire una pulizia implicita utilizzando il metodo Finalize. Finalize fornisce un backup per impedire la perdita permanente delle risorse se il programmatore non riesce a chiamare Dispose.
La principale differenza tra Dispose e Finalize è che:
Dispose
viene solitamente chiamato dal tuo codice. Le risorse vengono liberate all'istante quando lo chiami. Le persone dimenticano di chiamare il metodo, quindi using() {}
viene inventata la dichiarazione. Quando il programma termina l'esecuzione del codice all'interno di {}
, chiamerà il Dispose
metodo automaticamente.
Finalize
non viene chiamato dal tuo codice. È inteso per essere chiamato dal Garbage Collector (GC). Ciò significa che la risorsa potrebbe essere liberata in qualsiasi momento in futuro ogni volta che GC decide di farlo. Quando GC farà il suo lavoro, passerà attraverso molti metodi Finalize. Se hai una logica pesante in questo, rallenterà il processo. Potrebbe causare problemi di prestazioni per il tuo programma. Quindi fai attenzione a ciò che hai messo lì.
Personalmente scriverei la maggior parte della logica di distruzione in Dispose. Spero che questo chiarisca la confusione.
Come sappiamo, disporre e finalizzare sono entrambi utilizzati per liberare risorse non gestite .. ma la differenza è finalizzare utilizza due cicli per liberare le risorse, mentre come disporre utilizza un ciclo ..
Per rispondere nella prima parte, devi fornire esempi in cui le persone usano un approccio diverso per lo stesso oggetto di classe. Altrimenti è difficile (o addirittura strano) rispondere.
Per quanto riguarda la seconda domanda, leggi prima questo uso corretto dell'interfaccia IDisposable che afferma che
È la vostra scelta! Ma scegli Dispose.
In altre parole: il GC conosce solo il finalizzatore (se presente. Conosciuto anche come distruttore di Microsoft). Un buon codice tenterà di ripulire da entrambi (finalizzatore e smaltimento).