Questo articolo Γ¨ una libera traduzione di questa domanda di stackoverflow.
1. Introduzione
Il metodo Dispose()
viene usato per liberare risorse non piΓΉ gestite che non possono essere rilevate dal Garbage Collector (chiamate in inglese unmanaged, per maggiore chiarezza dβora in poi userΓ² questo termine).
Eβ importante sottolineare che il .NET possiede di garbage collector per lβeliminazione delle istanze di classi non piΓΉ referenziate, analogamente al Java.
Lβeliminazione delle risorse dalla memoria deve essere eseguito ad un certo punto del codice, altrimenti tali risorse rimarranno in RAM e potrebbero portare a dei memory leak.
2. Managed e unmanaged
Per capire la distinzione tra risorse managed e unmanaged basti pensare a questo:
- tutti i tipi di variabili che si possono trovare in .NET (quindi su MSDN) sono managed. Il GC conosce tutto di loro e puΓ² eliminarle in autonomia;
- tutti i tipi che invece non si trovano su MSDN sono unmanaged. Tutte le risorse che sono state create tramite chiamate di PInvoke (Platform Invocation Services) che quindi escono dal confortevole mondo dei tipi .NET Γ¨ unmanaged e, conseguentemente, si ha anche la responsabilitΓ della loro eliminazione dalla memoria.
Assumiamo per esempio di avere una variabile di tipo intPtr
. Il Garbage Collector non conosce come chiamare il metodo DeleteHandle()
(che Γ¨ il suo metodo tipico di eliminazione di oggetti), ne se chiamare o meno tale metodo.
3. Lβinterfaccia IDisposable
Lβoggetto creato deve esporre un metodo che puΓ² essere chiamato dallβesterno che permette di eliminare questa risorsa unmanaged. Questo metodo Γ¨ chiamato Dispose()
e, per rendere piΓΉ chiara la struttura del codice, esiste unβinterfaccia, chiamata IDisposable
che ha solo un metodo, il Dipose()
.
public interface IDisposable
{
void Dispose()
}
Esempio di implementazione del metodo Dispose()
.
public void Dispose()
{
Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
}
4. Dipose di oggetti managed
Assumiamo di lavorare con oggetti managed, per esempio una System.Drawing.Bitmap
. Ora, questa puΓ² occupare anche, per esempio, 250MB di memora.
Ovviamente questo Γ¨ un oggetto managed e quindi il GC prima o poi lo eliminerΓ .
Ora, davvero noi vogliamo che ci siano 250MB di memoria occupati per nulla, nellβattesa che eventualmente il GC arrivi e la liberi? Ovviamente no.
Il metodo Dispose()
di un oggetto deve quindi eliminare
- le risorse unmanaged (perchè è obbligato a farlo)
- le risorse managed (perchΓ¨ puΓ² essere utile per lβapplicazione stessa)
Aggiorniamo il metodo Dispose()
indicato sopra con lβeliminazione anche delle risorse managed, come una connessione a DB e un frame buffer.
public void Dispose()
{
//Free unmanaged resources
Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
//Free managed resources too
if (this.databaseConnection != null)
{
this.databaseConnection.Dispose();
this.databaseConnection = null;
}
if (this.frameBufferImage != null)
{
this.frameBufferImage.Dispose();
this.frameBufferImage = null;
}
}
5. Il metodo Finalize()
Assumiamo ora che il programmatore dimentichi di chiamare il metodo Dispose()
sullβoggetto che ho creato. AvrΓ² un leak di risorse unmanaged, eventualitΓ ovviamente da evitare!
Ovviamente le risorse managed verranno eliminate prima o poi dal GC, anche senza chiamare il metodo Dispose()
, portando perΓ² alle problematiche descritte sopra.
Vorremo avere un modo per cui, quando il GC elimina il mio oggetto, ne elimini anche le risorse unmanaged senza rendersene conto.
Quando il GC elimina tutti gli oggetti managed, chiama il metodo Finalize()
su ogni oggetto. Il GC non conosce il nostro metodo Dispose()
.
++Per fare in modo che, quando il GC elimina le risorse managed, vengano eliminate anche le risorse unmanaged Γ¨ lβoverride del metodo Finalize()
++.
In C#, non devo esplicitamente fare un override del metodo Finalize()
. Invece per fare ciΓ² scrivo un metodo che ha la stessa sintassi di un distruttore C++, sarΓ il compilatore a tradurlo in un implementazione del metodo Finalize()
.
Per esempio posso avere una situazione analoga (attenzione, lβesempio successivo ha un bug!)
~MyObject()
{
//we're being finalized (i.e. destroyed), call Dispose in case the user forgot to
Dispose(); //<--Warning: subtle bug! Keep reading!
}
Il codice indicato sopra ha perΓ² un problema: come sappiamo, il GC lavora con un thread in background, conseguentemente non conosce lβordine in cui due oggetti vengono distrutti.
Conseguentemente Γ¨ possibile che, nel codice del mio Dispose()
gli oggetti managed che sto cercando di eliminare siano a tutti gli effetti giΓ stati eliminati dal GC.
Alle chiamate di:
this.databaseConnection.Dispose();
o
this.frameBufferImage.Dispose();
avrei un crash dellβapplicazione, in quanto sto cercando di eliminare risorse che sono giΓ state eliminate.
Conseguentemente devo avere un modo per cui il metodo Finalize()
comunica al metodo Dispose()
che questo non deve toccare le risorse managed in quanto potrebbero non essere più lì, mentre deve agire normalmente per le risorse unmanaged.
Il pattern standard per ottenere ciΓ² Γ¨ che sia il metodo Finalize()
che il metodo Dispose()
chiamino un terzo metodo con un parametro booleano che indica se Γ¨ stato chiamato dal metodo Dispose()
o dal metodo Finalize()
.
Eβ prassi comune chiamare questo metodo con la seguente firma:
protected void Dispose(Boolean disposing)
anche se sarebbe piΓΉ esplicativo scrivere:
protected void Dispose(Boolean itIsSafeToAlsoFreeManagedObjects)
Ottenendo quindi:
public void Dispose()
{
Dispose(true); //I am calling you from Dispose, it's safe
}
e
~MyObject()
{
Dispose(false); //I am *not* calling you from Dispose, it's *not* safe
}
N.b. Se sto facendo lβoverride di un metodo Dispose()
della mia superclasse, devo ricordare di chiamare anche tale metodo:
public Dispose()
{
try
{
Dispose(true); //true: safe to free managed resources
}
finally
{
base.Dispose();
}
}
6. Gestione del Garbage Collector
Per come abbiamo strutturato il codice, quando lβutente chiama il metodo Dispose()
su un oggetto, ogni cosa viene eliminata dalla memoria.
Successivamente, quando arriva il Garbage Collector e chiama il metodo Finalize()
verrΓ eseguita una ulteriore chiamata al metodo Dispose()
Questa modalitΓ non solo Γ¨ uno spreco di risorse, ma se il mio oggetto ha riferimenti ad oggeti che sono giΓ stati eliminati dalβultima chiamta del metodo Dispose()
cercherΓ² di eliminarli ancora!
Faccio notare che nel codice indicato sopra, ho la rimozione dei riferimenti ad oggetti eliminati:
this.frameBufferImage.Dispose();
this.frameBufferImage = null;
conseguentemente non avrei questo bug, perΓ² qualora dimenticassi di assegnare a null
la variabile avrei il problema.
Il modo per risolvere questo comportamento Γ¨ dire al Garbage collector che non deve chiamare il metodo Finalize()
di questo oggetto, in quanto le sue risorse sono giΓ state eliminate in precedenza.
Questo comportamento Γ¨ implementato grazie al metodo GC.SuppressFinalize()
:
public void Dispose()
{
Dispose(true); //I am calling you from Dispose, it's safe
GC.SuppressFinalize(this); //Hey, GC: don't bother calling finalize later
}
7. Perchè non liberare le risorse unmanaged nel Finalize()
In teoria io potre avere lβeliminazione delle risorse unmanaged anche nel metodo Finalize()
ma Γ¨ sempre consigliabile usare il metodo Dispose()
Questo perchè il metodo Finalize()
viene chiamato solo dal GC, e io non ho idea di quando questo opererΓ , mi affido alle sue tempistiche per lβeliminazione di oggeti che potrebbero potenzialmente portare a dei leak.
La documentazione di Object.Finalize Γ¨ infatti chiara: il metodo Finalize()
viene chiamato con tempistiche non deterministiche. per avere eliminazione a tempistiche note, Γ¨ necessario avere un implementazione del metodo Dispose()
di IDisposable
.
8. Codice Finale
Di seguito indico il pattern completo dellβimplementazione di una classe IDisposable
.
class BaseClass : IDisposable
{
// Flag: Has Dispose already been called?
bool disposed = false;
// Public implementation of Dispose pattern callable by consumers.
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
// Protected implementation of Dispose pattern.
protected virtual void Dispose(bool disposing)
{
if (disposed)
return;
if (disposing) {
// Free any other managed objects here.
//
}
// Free any unmanaged objects here.
//
disposed = true;
}
~BaseClass()
{
Dispose(false);
}
}