Implementare un metodo Dispose

L'implementazione Dispose del metodo è principalmente per il rilascio di risorse non gestite. Quando si lavora con membri di istanza IDisposable che sono implementazioni, è comune eseguire chiamate a Dispose catena. Esistono altri motivi per implementare , ad esempio per liberare memoria allocata, rimuovere un elemento aggiunto a una raccolta o segnalare il rilascio di un blocco Dispose acquisito.

Il Garbage Collector .NET non alloca né rilascia memoria non gestita. Il modello per l'eliminazione di un oggetto, definito modello dispose, impone l'ordine per la durata di un oggetto. Il modello dispose viene usato per gli oggetti che implementano l'interfaccia ed è 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 IDisposable gestita. Ciò è dovuto al fatto che il Garbage Collector non è in grado di recuperare oggetti non gestiti.

Per garantire che le risorse siano sempre pulite in modo appropriato, un metodo deve essere idempotente, in modo che sia chiamabile più volte senza generare Dispose un'eccezione. Inoltre, le chiamate successive di Dispose non devono eseguire alcuna operazione.

L'esempio di codice fornito per il metodo mostra come l'operazione di Garbage Collection può causare l'esecuzione di un finalizzatore, mentre è ancora in uso un riferimento non gestito all'oggetto o ai GC.KeepAlive relativi membri. Può essere opportuno usare per rendere l'oggetto non idoneo per l'operazione di Garbage Collection dall'inizio della routine corrente al punto in cui viene GC.KeepAlive chiamato questo metodo.

Suggerimento

Per quanto riguarda l'inserimento delle dipendenze, quando si registrano i servizi in , la durata del servizio viene IServiceCollection gestita in modo implicito per conto dell'utente. e IServiceProvider corrispondente IHost orchestrano la pulizia delle risorse. In particolare, le IDisposable implementazioni di IAsyncDisposable e vengono correttamente eliminati alla fine della durata specificata.

Per altre informazioni, vedere Inserimento delle dipendenze in .NET.

Cassaforte handle

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 che identifica una risorsa non System.IntPtr gestita. In Windows potrebbe identificare un handle in Unix, un descrittore di file. Fornisce tutta la logica necessaria per garantire che questa risorsa venga rilasciata una sola volta, quando l'oggetto viene eliminato o quando tutti i riferimenti a sono stati eliminati e l'istanza viene SafeHandle SafeHandle SafeHandle 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, deriva da per eseguire il wrapping che identifica gli handle/descrittori di file aperti ed esegue l'override del relativo metodo per chiuderlo (tramite la funzione in Unix o funzione SafeFileHandle SafeHandle su IntPtrs SafeHandle.ReleaseHandle() close CloseHandle Windows). La maggior parte delle API nelle librerie .NET che creano una risorsa non gestita la esegue il wrapping in un oggetto e la restituisce in base alle esigenze, anziché restituire il puntatore SafeHandle SafeHandle non elaborato. Nelle situazioni in cui si interagisce con un componente non gestito e si ottiene un oggetto per una risorsa non gestita, è possibile creare un tipo personalizzato per IntPtr SafeHandle il wrapping. Di conseguenza, pochi tipi non devono implementare finalizzatori. La maggior parte delle implementazioni del modello usa e getta termina solo con il wrapping di altre risorse gestite, alcune delle quali SafeHandle SafeHandle possono essere s.

Le seguenti classi derivate nello spazio dei nomi Microsoft.Win32.SafeHandles forniscono handle sicuri:

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 Dispose(bool) di overload aggiuntivo.

Le firme dei metodi sono:

  • publicnon virtuale ( NotOverridable in Visual Basic) ( IDisposable.Dispose implementazione).
  • protected virtual( Overridable in Visual Basic) Dispose(bool) .

Metodo Dispose()

Poiché il metodo , non virtuale ( in Visual Basic), senza parametri viene chiamato quando non è più necessario (da un consumer del tipo), lo scopo è liberare risorse non gestite, eseguire la pulizia generale e indicare che il finalizzatore, se presente, non deve essere public NotOverridable Dispose 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. Si noti che la pulizia effettiva viene eseguita Dispose(bool) dall'overload del metodo .

Overload del metodo Dispose(bool)

Nell'overload il parametro è un che indica se la chiamata al metodo proviene da un metodo (il relativo valore è ) o da un disposing Boolean finalizzatore Dispose true (il relativo 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 disposing parametro deve essere quando viene chiamato da un false finalizzatore e true quando viene chiamato dal metodo IDisposable.Dispose . In altre parole, è quando viene chiamato in modo true 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à stato 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 Dispose implementazione (eliminazione a catena). Se è stata usata una classe derivata di per System.Runtime.InteropServices.SafeHandle eseguire il wrapping della risorsa non gestita, è necessario chiamare SafeHandle.Dispose() l'implementazione qui.

    • Oggetti gestiti che usano grandi quantità di memoria o risorse insufficienti. Assegnare riferimenti a oggetti gestiti di grandi dimensioni a per renderli più null probabili. In questo modo vengono rilasciate più velocemente rispetto a quando vengono 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 della verifica che il percorso falso non interagisca con gli oggetti gestiti che potrebbero essere stati recuperati. 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 , anche la IDisposable classe contenitore deve implementare IDisposable . Anche una classe che crea un'istanza di un'implementazione e la archivia come membro di istanza è responsabile IDisposable della pulizia. Ciò consente di garantire che ai tipi eliminabili a cui si fa riferimento sia data la possibilità di eseguire in modo deterministico la pulizia tramite il Dispose metodo . In questo esempio 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

Implementare il modello dispose

Tutte le classi non sealed (o Visual Basic non modificate come ) devono essere considerate una potenziale classe di base, perché potrebbero NotInheritable essere ereditate. Se si implementa il modello dispose per qualsiasi classe di 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 SafeHandle classe fornisce un finalizzatore, quindi non è necessario scriverne uno manualmente.

Importante

Una classe di base può fare riferimento solo a oggetti gestiti e implementare 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 del modello generale per l'implementazione del modello dispose per una classe di base che usa un handle sicuro.

using Microsoft.Win32.SafeHandles;
using System;
using System.Runtime.InteropServices;

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

    // Protected implementation of Dispose pattern.
    protected virtual void Dispose(bool disposing)
    {
        if (!_disposedValue)
        {
            if (disposing)
            {
                _safeHandle.Dispose();
            }

            _disposedValue = true;
        }
    }
}
Imports Microsoft.Win32.SafeHandles
Imports System.Runtime.InteropServices

Class BaseClassWithSafeHandle : Implements IDisposable
    ' Flag: Has Dispose already been called?
    Dim disposed As Boolean = False
    ' Instantiate a SafeHandle instance.
    Dim handle 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(disposing As Boolean)
        If disposed Then Return

        If disposing Then
            handle.Dispose()
        End If

        disposed = True
    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;

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;
        }
    }
}
Class BaseClassWithFinalizer : Implements IDisposable
    ' Flag: Has Dispose already been called?
    Dim disposed As Boolean = False

    ' 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(disposing As Boolean)
        If disposed Then Return

        If disposing Then
            ' Dispose managed objects that implement IDisposable.
            ' Assign null to managed objects that consume large amounts of memory or consume scarce resources.
        End If

        ' Free any unmanaged objects here.
        '
        disposed = True
    End Sub

    Protected Overrides Sub Finalize()
        Dispose(False)
    End Sub
End Class

Suggerimento

In C# viene implementata una finalizzazione fornendo un finalizzatore, non eseguendo l'override di Object.Finalize . In Visual Basic creare 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 una classe derivata, è invece necessario specificare 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 chiamare anche il metodo ( in Visual Basic) passando lo stato base.Dispose(bool) MyBase.Dispose(bool) di eliminazione bool disposing (parametro) 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 Dispose(bool) l'overload con false l'argomento .

Di seguito è riportato un esempio del modello generale per l'implementazione del modello dispose per una classe derivata che usa un handle sicuro:

using Microsoft.Win32.SafeHandles;
using System;
using System.Runtime.InteropServices;

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

            _disposedValue = true;
        }

        // Call base class implementation.
        base.Dispose(disposing);
    }
}
Imports Microsoft.Win32.SafeHandles
Imports System.Runtime.InteropServices

Class DerivedClassWithSafeHandle : Inherits BaseClassWithSafeHandle
    ' Flag: Has Dispose already been called?
    Dim disposed As Boolean = False
    ' Instantiate a SafeHandle instance.
    Dim handle As SafeHandle = New SafeFileHandle(IntPtr.Zero, True)

    ' Protected implementation of Dispose pattern.
    Protected Overrides Sub Dispose(disposing As Boolean)
        If disposed Then Return

        If disposing Then
            handle.Dispose()
            ' Free any other managed objects here.
            '
        End If

        ' Free any unmanaged objects here.
        '
        disposed = True

        ' 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:

class DerivedClassWithFinalizer : BaseClassWithFinalizer
{
    // To detect redundant calls
    private bool _disposedValue;

    ~DerivedClassWithFinalizer() => this.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);
    }
}
Class DerivedClassWithFinalizer : Inherits BaseClassWithFinalizer
    ' Flag: Has Dispose already been called?
    Dim disposed As Boolean = False

    ' Protected implementation of Dispose pattern.
    Protected Overrides Sub Dispose(disposing As Boolean)
        If disposed Then Return

        If disposing Then
            ' Dispose managed objects that implement IDisposable.
            ' Assign null to managed objects that consume large amounts of memory or consume scarce resources.
        End If

        ' Free any unmanaged objects here.
        '
        disposed = True

        ' Call the base class implementation.
        MyBase.Dispose(disposing)
    End Sub

    Protected Overrides Sub Finalize()
        Dispose(False)
    End Sub
End Class

Vedi anche