Implementare un metodo Dispose
Il metodo Dispose viene implementato principalmente per rilasciare risorse non gestite. Quando si lavora con i membri dell'istanza che sono implementazioni IDisposable, è comune eseguire chiamate Dispose a catena. Esistono altri motivi per l'implementazione di Dispose, ad esempio, per liberare memoria allocata, rimuovere un elemento aggiunto a una raccolta o segnalare il rilascio di un blocco acquisito.
Il Garbage Collector .NET non alloca o rilascia memoria non gestita. Il criterio per eliminare un oggetto, definito criterio Dispose, definisce un ordine in base alla durata di un oggetto. Il criterio Dispose viene usato per gli oggetti che implementano l'interfaccia IDisposable. Questo modello è comune quando si interagisce con handle di file e pipe, handle del Registro di sistema, handle di attesa o puntatori a blocchi di memoria non gestita, perché il Garbage Collector non è in grado di recuperare oggetti non gestiti.
Al fine di garantire la corretta pulitura delle risorse in ogni occasione, un metodo Dispose deve essere idempotente, in modo che si possa chiamare più volte senza che venga generata un'eccezione. Inoltre, le chiamate successive di Dispose non dovrebbero eseguire alcuna operazione.
L'esempio di codice fornito per il metodo GC.KeepAlive mostra in che modo un finalizzatore può causare l'esecuzione di un finalizzatore mentre un riferimento non gestito all'oggetto, o i relativi membri, è ancora in uso. Può essere utile usare GC.KeepAlive per rendere l'oggetto non idoneo per l'operazione di Garbage Collection dall'inizio della routine corrente al punto in cui viene chiamato questo metodo.
Suggerimento
Per quanto riguarda l'inserimento delle dipendenze, quando si registrano dei servizi in un oggetto IServiceCollection, la durata del servizio viene gestita in modo implicito per conto dell'utente. IServiceProvider e IHost corrispondente orchestrano la pulizia delle risorse. In particolare, le implementazioni di IDisposable e IAsyncDisposable vengono eliminate correttamente alla fine della durata specificata.
Per altre informazioni, vedere Inserimento delle dipendenze in .NET.
Handle sicuri
La scrittura di codice per il finalizzatore di un oggetto è un'attività complessa che può causare problemi se non eseguita correttamente. È pertanto consigliabile costruire oggetti System.Runtime.InteropServices.SafeHandle anziché implementare un finalizzatore.
System.Runtime.InteropServices.SafeHandle è un tipo gestito astratto che esegue il wrapping di un oggetto System.IntPtr che identifica una risorsa non gestita. In Windows potrebbe identificare un handle, e in Unix un descrittore di file. SafeHandle
fornisce tutta la logica necessaria per garantire che questa risorsa venga rilasciata una volta e una sola volta, quando SafeHandle
viene eliminato o quando tutti i riferimenti a SafeHandle
sono stati eliminati e l'istanza SafeHandle
viene finalizzata.
System.Runtime.InteropServices.SafeHandle è una classe di base astratta. Le classi derivate forniscono istanze specifiche per diversi tipi di handle. Queste classi derivate convalidano quali valori per System.IntPtr sono considerati non validi e come liberare effettivamente l'handle. Ad esempio, SafeFileHandle deriva da SafeHandle
per eseguire il wrapping di IntPtrs
che identifica handle/descrittori di file aperti ed esegue l'override del relativo metodo SafeHandle.ReleaseHandle() per chiuderlo (tramite la funzione close
in Unix o la funzione CloseHandle
in Windows). La maggior parte delle API nelle librerie .NET che creano una risorsa non gestita esegue il wrapping in un SafeHandle
oggetto e lo restituisce SafeHandle
in base alle esigenze, invece di restituire il puntatore non elaborato. Nelle situazioni in cui si interagisce con un componente non gestito e si ottiene un oggetto IntPtr
per una risorsa non gestita, è possibile creare il proprio tipo SafeHandle
per eseguirne il wrapping. Di conseguenza, alcuni tipi non SafeHandle
devono implementare i finalizzatori. La maggior parte delle implementazioni di modelli eliminabili comporta solo il wrapping di altre risorse gestite, alcune delle quali possono essere oggetti SafeHandle
.
Le seguenti classi derivate nello spazio dei nomi Microsoft.Win32.SafeHandles forniscono handle sicuri.
Classe | Risorse che contiene |
---|---|
SafeFileHandle SafeMemoryMappedFileHandle SafePipeHandle |
File, file mappati alla memoria e pipe |
SafeMemoryMappedViewHandle | Visualizzazioni di memoria |
SafeNCryptKeyHandle SafeNCryptProviderHandle SafeNCryptSecretHandle |
Costrutti di crittografia |
SafeRegistryHandle | Chiavi del Registro di sistema |
SafeWaitHandle | Handle di attesa |
Dispose() e Dispose(bool)
L'interfaccia IDisposable richiede l'implementazione di un singolo metodo senza parametri, Dispose. Inoltre, qualsiasi classe non sealed deve avere un metodo di overload Dispose(bool)
.
Le firme del metodo sono:
public
non virtuale (NotOverridable
in Visual Basic) (implementazione IDisposable.Dispose).protected virtual
(Overridable
in Visual Basic)Dispose(bool)
.
Metodo Dispose()
Poiché il metodo public
, non virtuale (NotOverridable
in Visual Basic), Dispose
senza parametri viene chiamato quando non è più necessario (da un consumer del tipo), il suo scopo è liberare risorse non gestite, eseguire la pulizia generale e indicare che il finalizzatore, se presente, non deve essere eseguito. Liberare la memoria effettiva associata a un oggetto gestito è sempre il dominio del Garbage Collector. Per questo motivo il metodo ha un'implementazione standard:
public void Dispose()
{
// Dispose of unmanaged resources.
Dispose(true);
// Suppress finalization.
GC.SuppressFinalize(this);
}
Public Sub Dispose() _
Implements IDisposable.Dispose
' Dispose of unmanaged resources.
Dispose(True)
' Suppress finalization.
GC.SuppressFinalize(Me)
End Sub
Il metodo Dispose
esegue la pulizia di tutti gli oggetti, quindi il Garbage Collector non deve più chiamare l'override Object.Finalize degli oggetti. Pertanto, la chiamata al metodo SuppressFinalize impedisce al Garbage Collector di eseguire il finalizzatore. Se il tipo non dispone di un finalizzatore, la chiamata a GC.SuppressFinalize non ha alcun effetto. La pulizia effettiva viene eseguita dall'overload del metodo Dispose(bool)
.
Overload del metodo Dispose(bool)
Nell’overload, il parametro disposing
è un oggetto Boolean che indica se la chiamata al metodo proviene da un metodo Dispose (il valore è true
) o da un finalizzatore (il valore è false
).
protected virtual void Dispose(bool disposing)
{
if (_disposed)
{
return;
}
if (disposing)
{
// TODO: dispose managed state (managed objects).
}
// TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
// TODO: set large fields to null.
_disposed = true;
}
Protected Overridable Sub Dispose(disposing As Boolean)
If disposed Then Exit Sub
' A block that frees unmanaged resources.
If disposing Then
' Deterministic call…
' A conditional block that frees managed resources.
End If
disposed = True
End Sub
Importante
Il parametro disposing
deve essere false
quando viene chiamato da un finalizzatore, e true
quando viene chiamato dal metodo IDisposable.Dispose. In altre parole, è true
quando viene chiamato in modo deterministico e false
quando viene chiamato in modo non deterministico.
Il corpo del metodo è costituito da tre blocchi di codice:
Blocco per la restituzione condizionale se l'oggetto è già eliminato.
Un blocco che libera le risorse non gestite. Questo blocco viene eseguito indipendentemente dal valore del parametro
disposing
.Un blocco condizionale che libera le risorse gestite. Il blocco è eseguito se il valore di
disposing
ètrue
. Le risorse gestite liberate possono includere:Oggetti gestiti che implementano IDisposable. Il blocco condizionale può essere usato per chiamare la relativa implementazione Dispose (eliminazione a catena). Se è stata usata una classe derivata di System.Runtime.InteropServices.SafeHandle per eseguire il wrapping della risorsa non gestita, è necessario chiamare l'implementazione SafeHandle.Dispose() qui.
Oggetti gestiti che usano grandi quantità di memoria o risorse insufficienti. Assegnare riferimenti a oggetti gestiti di grandi dimensioni a
null
per renderli più facilmente irraggiungibili. Queste versioni vengono rilasciate più velocemente rispetto a quelle recuperate in modo non deterministico.
Se la chiamata al metodo proviene da un finalizzatore, deve essere eseguito solo il codice che libera le risorse non gestite. L'implementatore è responsabile di garantire che il percorso false non interagisca con gli oggetti gestiti che potrebbero essere stati eliminati. Questo è importante perché l'ordine in cui il Garbage Collector elimina gli oggetti gestiti durante la finalizzazione non è deterministico.
Chiamate dispose a catena
Se la classe è proprietaria di un campo o di una proprietà e il relativo tipo implementa IDisposable, anche la stessa classe contenitore deve implementare IDisposable. Una classe che crea un'implementazione IDisposable e la archivia come membro dell'istanza è anche responsabile della pulizia. Ciò consente di garantire che ai tipi eliminabili a cui si fa riferimento sia data la possibilità di eseguire la pulizia in modo deterministico tramite il metodo Dispose. Nell'esempio seguente la classe è sealed
(o NotInheritable
in Visual Basic).
using System;
public sealed class Foo : IDisposable
{
private readonly IDisposable _bar;
public Foo()
{
_bar = new Bar();
}
public void Dispose() => _bar.Dispose();
}
Public NotInheritable Class Foo
Implements IDisposable
Private ReadOnly _bar As IDisposable
Public Sub New()
_bar = New Bar()
End Sub
Public Sub Dispose() Implements IDisposable.Dispose
_bar.Dispose()
End Sub
End Class
Suggerimento
- Se la classe ha un campo o una proprietà IDisposable ma non ne è proprietaria, ovvero la classe non crea l'oggetto, la classe non ha bisogno di implementare IDisposable.
- Esistono casi in cui è possibile eseguire il controllo di
null
in un finalizzatore (che include il metodoDispose(false)
richiamato da un finalizzatore). Uno dei motivi principali è se non si è certi che l'istanza sia stata inizializzata completamente (ad esempio, un'eccezione potrebbe essere generata in un costruttore).
Implementare lo schema Dispose
Tutte le classi non sealed (o le classi Visual Basic non modificate come NotInheritable
) devono essere considerate una potenziale classe di base, perché potrebbero essere ereditate. Se si implementa il modello dispose per qualsiasi classe base potenziale, è necessario specificare quanto segue:
- Un'implementazione Dispose che chiami il metodo
Dispose(bool)
. - Metodo
Dispose(bool)
che esegue la pulizia effettiva. - Una classe derivata da SafeHandle che esegua il wrapping della risorsa non gestita (consigliato) o un override al metodo Object.Finalize. La classe SafeHandle fornisce un finalizzatore, quindi non è necessario scriverne uno manualmente.
Importante
È possibile che una classe di base faccia riferimento solo a oggetti gestiti e implementi il modello dispose. In questi casi, un finalizzatore non è necessario. Un finalizzatore è necessario solo se si fa riferimento direttamente a risorse non gestite.
Di seguito è riportato un esempio generale di implementazione del modello dispose per una classe di base che usa un handle sicuro.
using Microsoft.Win32.SafeHandles;
using System;
using System.Runtime.InteropServices;
public class BaseClassWithSafeHandle : IDisposable
{
// To detect redundant calls
private bool _disposedValue;
// Instantiate a SafeHandle instance.
private SafeHandle? _safeHandle = new SafeFileHandle(IntPtr.Zero, true);
// 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 (!_disposedValue)
{
if (disposing)
{
_safeHandle?.Dispose();
_safeHandle = null;
}
_disposedValue = true;
}
}
}
Imports Microsoft.Win32.SafeHandles
Imports System.Runtime.InteropServices
Public Class BaseClassWithSafeHandle
Implements IDisposable
' To detect redundant calls
Private _disposedValue As Boolean
' Instantiate a SafeHandle instance.
Private _safeHandle As SafeHandle = New SafeFileHandle(IntPtr.Zero, True)
' Public implementation of Dispose pattern callable by consumers.
Public Sub Dispose() _
Implements IDisposable.Dispose
Dispose(True)
GC.SuppressFinalize(Me)
End Sub
' Protected implementation of Dispose pattern.
Protected Overridable Sub Dispose(ByVal disposing As Boolean)
If Not _disposedValue Then
If disposing Then
_safeHandle?.Dispose()
_safeHandle = Nothing
End If
_disposedValue = True
End If
End Sub
End Class
Nota
L'esempio precedente usa un oggetto SafeFileHandle per illustrato il criterio; sarebbe possibile usare invece qualsiasi oggetto derivato da SafeHandle. Si noti che l'esempio non crea correttamente un'istanza del relativo oggetto SafeFileHandle.
Di seguito è illustrato il modello generale per implementare il modello Dispose per una classe di base che esegue l'override di Object.Finalize.
using System;
public class BaseClassWithFinalizer : IDisposable
{
// To detect redundant calls
private bool _disposedValue;
~BaseClassWithFinalizer() => Dispose(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 (!_disposedValue)
{
if (disposing)
{
// TODO: dispose managed state (managed objects)
}
// TODO: free unmanaged resources (unmanaged objects) and override finalizer
// TODO: set large fields to null
_disposedValue = true;
}
}
}
Public Class BaseClassWithFinalizer
Implements IDisposable
' To detect redundant calls
Private _disposedValue As Boolean
Protected Overrides Sub Finalize()
Dispose(False)
End Sub
' Public implementation of Dispose pattern callable by consumers.
Public Sub Dispose() _
Implements IDisposable.Dispose
Dispose(True)
GC.SuppressFinalize(Me)
End Sub
' Protected implementation of Dispose pattern.
Protected Overridable Sub Dispose(ByVal disposing As Boolean)
If Not _disposedValue Then
If disposing Then
' TODO: dispose managed state (managed objects)
End If
' TODO free unmanaged resources (unmanaged objects) And override finalizer
' TODO: set large fields to null
_disposedValue = True
End If
End Sub
End Class
Suggerimento
In C# si implementa una finalizzazione fornendo un finalizzatore, non eseguendo l'override di Object.Finalize. In Visual Basic si crea un finalizzatore con Protected Overrides Sub Finalize()
.
Implementare il modello dispose per una classe derivata
Una classe derivata da una classe che implementa l'interfaccia IDisposable non deve implementare IDisposable, poiché l'implementazione della classe di base di IDisposable.Dispose viene ereditata dalle classi derivate. Per pulire invece una classe derivata, fornire quanto segue:
- Metodo
protected override void Dispose(bool)
che esegue l'override del metodo della classe base ed esegue la pulizia effettiva della classe derivata. Questo metodo deve anche chiamare il metodobase.Dispose(bool)
(MyBase.Dispose(bool)
in Visual Basic) passando lo stato di eliminazione (parametrobool disposing
) come argomento. - Una classe derivata da SafeHandle che esegua il wrapping della risorsa non gestita (consigliato) o un override al metodo Object.Finalize. La classe SafeHandle fornisce un finalizzatore, evitando la necessità di codificarne uno. Se si specifica un finalizzatore, deve chiamare l'overload di
Dispose(bool)
con argomentofalse
.
Ecco un esempio del modello generale per implementare il modello dispose per una classe derivata che usa un handle sicuro:
using Microsoft.Win32.SafeHandles;
using System;
using System.Runtime.InteropServices;
public class DerivedClassWithSafeHandle : BaseClassWithSafeHandle
{
// To detect redundant calls
private bool _disposedValue;
// Instantiate a SafeHandle instance.
private SafeHandle? _safeHandle = new SafeFileHandle(IntPtr.Zero, true);
// Protected implementation of Dispose pattern.
protected override void Dispose(bool disposing)
{
if (!_disposedValue)
{
if (disposing)
{
_safeHandle?.Dispose();
_safeHandle = null;
}
_disposedValue = true;
}
// Call base class implementation.
base.Dispose(disposing);
}
}
Imports Microsoft.Win32.SafeHandles
Imports System.Runtime.InteropServices
Public Class DerivedClassWithSafeHandle
Inherits BaseClassWithSafeHandle
' To detect redundant calls
Private _disposedValue As Boolean
' Instantiate a SafeHandle instance.
Private _safeHandle As SafeHandle = New SafeFileHandle(IntPtr.Zero, True)
Protected Overrides Sub Dispose(ByVal disposing As Boolean)
If Not _disposedValue Then
If disposing Then
_safeHandle?.Dispose()
_safeHandle = Nothing
End If
_disposedValue = True
End If
' Call base class implementation.
MyBase.Dispose(disposing)
End Sub
End Class
Nota
L'esempio precedente usa un oggetto SafeFileHandle per illustrato il criterio; sarebbe possibile usare invece qualsiasi oggetto derivato da SafeHandle. Si noti che l'esempio non crea correttamente un'istanza del relativo oggetto SafeFileHandle.
Di seguito è illustrato il modello generale per implementare il modello Dispose per una classe derivata che esegue l'override di Object.Finalize:
public class DerivedClassWithFinalizer : BaseClassWithFinalizer
{
// To detect redundant calls
private bool _disposedValue;
~DerivedClassWithFinalizer() => Dispose(false);
// Protected implementation of Dispose pattern.
protected override void Dispose(bool disposing)
{
if (!_disposedValue)
{
if (disposing)
{
// TODO: dispose managed state (managed objects).
}
// TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
// TODO: set large fields to null.
_disposedValue = true;
}
// Call the base class implementation.
base.Dispose(disposing);
}
}
Public Class DerivedClassWithFinalizer
Inherits BaseClassWithFinalizer
' To detect redundant calls
Private _disposedValue As Boolean
Protected Overrides Sub Finalize()
Dispose(False)
End Sub
' Protected implementation of Dispose pattern.
Protected Overrides Sub Dispose(ByVal disposing As Boolean)
If Not _disposedValue Then
If disposing Then
' TODO: dispose managed state (managed objects).
End If
' TODO free unmanaged resources (unmanaged objects) And override a finalizer below.
' TODO: set large fields to null.
_disposedValue = True
End If
' Call the base class implementation.
MyBase.Dispose(disposing)
End Sub
End Class