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);
}
}