Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Annotazioni
Questo contenuto viene ristampato con il permesso di Pearson Education, Inc. da Framework Design Guidelines: Conventions, Idioms e Pattern per Librerie .NET Riutilizzabili, 2a Edizione. Tale edizione è stata pubblicata nel 2008 e il libro è stato completamente rivisto nella terza edizione. Alcune informazioni in questa pagina potrebbero non essere aggiornate.
Tutti i programmi acquisiscono una o più risorse di sistema, ad esempio memoria, handle di sistema o connessioni di database, durante il corso dell'esecuzione. Gli sviluppatori devono prestare attenzione quando usano tali risorse di sistema, perché devono essere rilasciati dopo l'acquisizione e l'uso.
CLR fornisce il supporto per la gestione automatica della memoria. La memoria gestita (memoria allocata tramite l'operatore new
C# ) non deve essere rilasciata in modo esplicito. Viene rilasciato automaticamente dal Garbage Collector (GC). Ciò libera gli sviluppatori dall'attività noiosa e difficile di rilasciare memoria ed è stata una delle ragioni principali per la produttività senza precedenti offerta da .NET Framework.
Sfortunatamente, la memoria gestita è solo uno dei molti tipi di risorse di sistema. Le risorse diverse dalla memoria gestita devono comunque essere rilasciate in modo esplicito e vengono definite risorse non gestite. Il GC non è stato progettato specificamente per gestire tali risorse non gestite, il che significa che la responsabilità della gestione delle risorse non gestite è nelle mani degli sviluppatori.
La CLR fornisce un po' di aiuto nel rilascio di risorse non gestite. System.Object dichiara un metodo Finalize virtuale (detto anche finalizzatore) chiamato dal GC prima che la memoria dell'oggetto venga recuperata dal GC e possa essere sottoposta a override per rilasciare risorse non gestite. I tipi che eseguono l'override del finalizzatore vengono definiti tipi finalizzabili.
Anche se i finalizzatori sono efficaci in alcuni scenari di pulizia, presentano due svantaggi significativi:
Il finalizzatore viene chiamato quando GC rileva che un oggetto è idoneo per la raccolta. Ciò si verifica in un determinato periodo di tempo dopo che la risorsa non è più necessaria. Il ritardo tra quando lo sviluppatore potrebbe o desidera rilasciare la risorsa e il momento in cui la risorsa viene effettivamente rilasciata dal finalizzatore potrebbe essere inaccettabile nei programmi che acquisiscono molte risorse scarse (risorse che possono essere facilmente esaurite) o nei casi in cui le risorse sono costose da mantenere in uso (ad esempio, grandi buffer di memoria non gestiti).
Quando CLR deve chiamare un finalizzatore, deve rimandare la raccolta della memoria dell'oggetto fino al successivo round di Garbage Collection (i finalizzatori vengono eseguiti tra raccolte). Ciò significa che la memoria dell'oggetto (e tutti gli oggetti a cui fa riferimento) non verranno rilasciati per un periodo di tempo più lungo.
Pertanto, l'uso esclusivo di finalizzatori potrebbe non essere appropriato in molti scenari quando è importante recuperare le risorse non gestite il più rapidamente possibile, quando si gestiscono risorse scarse o in scenari con prestazioni elevate in cui il sovraccarico GC aggiunto della finalizzazione non è inaccettabile.
Il framework fornisce l'interfaccia System.IDisposable che deve essere implementata per fornire allo sviluppatore un modo manuale per rilasciare risorse non gestite non appena non sono necessarie. Fornisce inoltre il GC.SuppressFinalize metodo che può indicare al GC che un oggetto è stato eliminato manualmente e non deve più essere finalizzato, nel qual caso la memoria dell'oggetto può essere recuperata in precedenza. I tipi che implementano l'interfaccia IDisposable
vengono definiti tipi eliminabili.
Il modello Dispose è progettato per standardizzare l'utilizzo e l'implementazione dei finalizzatori e dell'interfaccia IDisposable
.
La motivazione principale per il modello è ridurre la complessità dell'implementazione dei Finalize metodi e Dispose . La complessità deriva dal fatto che i metodi condividono alcuni ma non tutti i percorsi di codice (le differenze sono descritte più avanti nel capitolo). Esistono inoltre motivi cronologici per alcuni elementi del modello correlati all'evoluzione del supporto del linguaggio per la gestione delle risorse deterministica.
✓ DO implementa il modello Dispose di base sui tipi contenenti istanze di tipi eliminabili. Per informazioni dettagliate sul modello di base, vedere la sezione Basic Dispose Pattern.
Se un tipo è responsabile della durata di altri oggetti eliminabili, anche gli sviluppatori hanno bisogno di un modo per eliminarli. L'uso del metodo del Dispose
contenitore è un modo pratico per rendere possibile questa operazione.
✓ IMPLEMENTARE il modello Dispose basic e fornire un finalizzatore sui tipi che contengono risorse che devono essere liberate in modo esplicito e che non dispongono di finalizzatori.
Ad esempio, il modello deve essere implementato sui tipi che archiviano buffer di memoria non gestiti. La sezione Tipi finalizzabili illustra le linee guida relative all'implementazione dei finalizzatori.
✓ PRENDERE IN CONSIDERAZIONE l'implementazione del modello Dispose di base sulle classi che non contengono risorse non gestite o oggetti eliminabili, ma probabilmente hanno sottotipi che lo fanno.
Un ottimo esempio di questa è la System.IO.Stream classe . Anche se si tratta di una classe base astratta che non contiene risorse, la maggior parte delle relative sottoclassi esegue questa operazione e, per questo motivo, implementa questo modello.
Modello base del pattern Dispose
L'implementazione di base del modello prevede l'implementazione dell'interfaccia System.IDisposable
e la dichiarazione del Dispose(bool)
metodo che implementa tutta la logica di pulizia delle risorse da condividere tra il Dispose
metodo e il finalizzatore facoltativo.
L'esempio seguente illustra una semplice implementazione del modello di base:
public class DisposableResourceHolder : IDisposable {
private SafeHandle resource; // handle to a resource
public DisposableResourceHolder() {
this.resource = ... // allocates the resource
}
public void Dispose() {
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing) {
if (disposing) {
if (resource!= null) resource.Dispose();
}
}
}
Il parametro disposing
booleano indica se il metodo è stato richiamato dall'implementazione IDisposable.Dispose
o dal finalizzatore. L'implementazione Dispose(bool)
deve controllare il parametro prima di accedere ad altri oggetti di riferimento, ad esempio il campo della risorsa nell'esempio precedente. Tali oggetti devono essere accessibili solo quando il metodo viene chiamato dall'implementazione IDisposable.Dispose
(quando il disposing
parametro è uguale a true). Se il metodo viene richiamato dal finalizzatore (disposing
è false), non è necessario accedere ad altri oggetti. Il motivo è che gli oggetti vengono finalizzati in un ordine imprevedibile e quindi potrebbero essere già stati finalizzati insieme a una delle loro dipendenze.
Inoltre, questa sezione si applica alle classi con una base che non implementa già il criterio Dispose. Se si eredita da una classe che implementa già il modello, è sufficiente eseguire l'override del Dispose(bool)
metodo per fornire logica aggiuntiva di pulizia delle risorse.
✓ DO dichiara un protected virtual void Dispose(bool disposing)
metodo per centralizzare tutta la logica correlata al rilascio di risorse non gestite.
Tutte le operazioni di pulizia delle risorse devono essere eseguite in questo metodo. Il metodo viene chiamato sia dal finalizzatore sia dal metodo IDisposable.Dispose
. Il parametro sarà false se viene richiamato dall'interno di un finalizzatore. Deve essere usato per assicurarsi che qualsiasi codice in esecuzione durante la finalizzazione non acceda ad altri oggetti finalizzabili. I dettagli sull'implementazione dei finalizzatori sono descritti nella sezione successiva.
protected virtual void Dispose(bool disposing) {
if (disposing) {
if (resource!= null) resource.Dispose();
}
}
✓ DO implementare l'interfaccia IDisposable
semplicemente chiamando Dispose(true)
seguito da GC.SuppressFinalize(this)
.
La chiamata a SuppressFinalize
deve verificarsi solo se Dispose(true)
viene eseguita correttamente.
public void Dispose(){
Dispose(true);
GC.SuppressFinalize(this);
}
X DO NOT rendere virtuale il metodo senza Dispose
parametri.
Il Dispose(bool)
metodo è quello che deve essere sottoposto a override da sottoclassi.
// bad design
public class DisposableResourceHolder : IDisposable {
public virtual void Dispose() { ... }
protected virtual void Dispose(bool disposing) { ... }
}
// good design
public class DisposableResourceHolder : IDisposable {
public void Dispose() { ... }
protected virtual void Dispose(bool disposing) { ... }
}
X DO NOT dichiara tutti gli overload del Dispose
metodo diverso da Dispose()
e Dispose(bool)
.
Dispose
deve essere considerata una parola riservata per codificare questo modello e prevenire confusione tra implementatori, utenti e compilatori. Alcuni linguaggi potrebbero scegliere di implementare automaticamente questo modello in determinati tipi.
✓ DO consente di chiamare il Dispose(bool)
metodo più di una volta. Il metodo potrebbe scegliere di non eseguire alcuna operazione dopo la prima chiamata.
public class DisposableResourceHolder : IDisposable {
bool disposed = false;
protected virtual void Dispose(bool disposing) {
if (disposed) return;
// cleanup
...
disposed = true;
}
}
X AVOID genera un'eccezione dall'interno Dispose(bool)
tranne in situazioni critiche in cui il processo contenitore è stato danneggiato (perdite, stato condiviso incoerente e così via).
Gli utenti si aspettano che una chiamata a Dispose
non generi un'eccezione.
Se Dispose
potesse generare un'eccezione, la logica di pulizia del blocco finally non verrà eseguita. Per risolvere questo problema, l'utente dovrebbe racchiudere ogni chiamata a Dispose
(all'interno del blocco finally!) in un blocco try, il che porta a gestori di pulizia decisamente complessi. Se si esegue il metodo Dispose(bool disposing)
, non generare mai un'eccezione se la disposizione non è true. In questo modo il processo verrà terminato se viene eseguito all'interno di un contesto finalizzatore.
✓ DO genera un ObjectDisposedException da qualsiasi membro che non può essere utilizzato dopo che l'oggetto è stato eliminato.
public class DisposableResourceHolder : IDisposable {
bool disposed = false;
SafeHandle resource; // handle to a resource
public void DoSomething() {
if (disposed) throw new ObjectDisposedException(...);
// now call some native methods using the resource
...
}
protected virtual void Dispose(bool disposing) {
if (disposed) return;
// cleanup
...
disposed = true;
}
}
✓ CONSIDERA di fornire il metodo Close()
, oltre a Dispose()
, se "close" è considerato un termine standard nel contesto specifico.
Quando fai così, è importante rendere l'implementazione Close
identica a Dispose
e prendere in considerazione l'implementazione del metodo IDisposable.Dispose
in modo esplicito.
public class Stream : IDisposable {
IDisposable.Dispose() {
Close();
}
public void Close() {
Dispose(true);
GC.SuppressFinalize(this);
}
}
Tipi finalizzabili
I tipi finalizzabili sono tipi che estendono il modello Dispose di base eseguendo l'override del finalizzatore e fornendo, nel metodo Dispose(bool)
, il percorso del codice di finalizzazione.
I finalizzatori sono notoriamente difficili da implementare correttamente, principalmente perché non è possibile fare certe ipotesi (normalmente valide) sullo stato del sistema durante l'esecuzione. Le linee guida seguenti devono essere prese in considerazione attentamente.
Si noti che alcune delle linee guida si applicano non solo al Finalize
metodo , ma a qualsiasi codice chiamato da un finalizzatore. Nel caso del modello Dispose di base definito in precedenza, ciò significa che la logica viene eseguita all'interno Dispose(bool disposing)
quando il disposing
parametro è false.
Se la classe di base è già finalizzabile e implementa il modello Dispose di base, non è consigliabile eseguire di nuovo l'override Finalize
. È invece consigliabile eseguire l'override del Dispose(bool)
metodo per fornire logica aggiuntiva di pulizia delle risorse.
Ecco un esempio di un tipo finalizzabile nel codice seguente:
public class ComplexResourceHolder : IDisposable {
private IntPtr buffer; // unmanaged memory buffer
private SafeHandle resource; // disposable handle to a resource
public ComplexResourceHolder() {
this.buffer = ... // allocates memory
this.resource = ... // allocates the resource
}
protected virtual void Dispose(bool disposing) {
ReleaseBuffer(buffer); // release unmanaged memory
if (disposing) { // release other disposable objects
if (resource!= null) resource.Dispose();
}
}
~ComplexResourceHolder() {
Dispose(false);
}
public void Dispose() {
Dispose(true);
GC.SuppressFinalize(this);
}
}
X AVOID rende i tipi finalizzabili.
Considerare attentamente qualsiasi caso in cui si ritiene che sia necessario un finalizzatore. Esiste un costo reale associato alle istanze con finalizzatori, sia dal punto di vista delle prestazioni che della complessità del codice. Preferire l'uso di wrapper di risorse, SafeHandle ad esempio per incapsulare le risorse non gestite laddove possibile, nel qual caso un finalizzatore diventa non necessario perché il wrapper è responsabile della propria pulizia delle risorse.
X DO NOT rendere finalizzabili i tipi valore.
Solo i tipi di riferimento vengono effettivamente finalizzati dal CLR e pertanto qualsiasi tentativo di posizionare un finalizzatore su un tipo di valore verrà ignorato. I compilatori C# e C++ applicano questa regola.
✓ DO rendere un tipo finalizzabile se il tipo è responsabile del rilascio di una risorsa non gestita che non dispone di un proprio finalizzatore.
Quando si implementa il finalizzatore, è sufficiente chiamare Dispose(false)
e inserire tutta la logica di pulizia delle risorse all'interno del Dispose(bool disposing)
metodo .
public class ComplexResourceHolder : IDisposable {
~ComplexResourceHolder() {
Dispose(false);
}
protected virtual void Dispose(bool disposing) {
...
}
}
✓ DO implementa il modello Dispose di base in ogni tipo finalizzabile.
In questo modo, gli utenti del tipo specifico possono eseguire la pulizia deterministica delle stesse risorse per cui il finalizzatore è responsabile in modo esplicito.
X DO NOT accedere a nessun oggetto finalizzabile nel percorso del codice finalizzatore, perché c'è un rischio significativo che siano già stati finalizzati.
Ad esempio, un oggetto finalizzabile A con un riferimento a un altro oggetto finalizzabile B non può usare in modo affidabile B nel finalizzatore di A o viceversa. I finalizzatori vengono chiamati in un ordine casuale, a meno di una garanzia di ordinamento debole per la finalizzazione critica.
Tenere inoltre presente che gli oggetti archiviati in variabili statiche verranno raccolti in determinati punti durante lo scaricamento di un dominio applicazione o durante l'uscita dal processo. L'accesso a una variabile statica che fa riferimento a un oggetto finalizzabile (o che chiama un metodo statico che potrebbe usare valori archiviati in variabili statiche) potrebbe non essere sicuro se Environment.HasShutdownStarted restituisce true.
✓ FARE in modo che il Finalize
metodo sia protetto.
Gli sviluppatori C#, C++e VB.NET non devono preoccuparsi di questo problema, perché i compilatori aiutano a applicare questa linea guida.
X DO NOT consente alle eccezioni di escape dalla logica del finalizzatore, ad eccezione degli errori critici del sistema.
Se viene generata un'eccezione da un finalizzatore, il CLR arresta l'intero processo (a partire da .NET Framework versione 2.0), impedendo l'esecuzione di altri finalizzatori e il rilascio controllato delle risorse.
✓ CONSIDERARE la creazione e l'uso di un oggetto finalizzabile critico (un tipo con una gerarchia di tipi che contiene CriticalFinalizerObject) per situazioni in cui un finalizzatore deve essere eseguito assolutamente anche in caso di scaricamento forzato del dominio applicativo e terminazione del thread.
© Porzioni 2005, 2009 Microsoft Corporation. Tutti i diritti riservati.
Ristampato dall'autorizzazione di Pearson Education, Inc. da Framework Design Guidelines: Conventions, Idioms e Patterns for Reusable .NET Libraries, 2nd Edition di Krzysztof Cwalina e Brad Abrams, pubblicato il 22 ottobre 2008 da Addison-Wesley Professional come parte della Serie di sviluppo di Microsoft Windows.