Freigeben über


Implementieren einer Dispose-Methode

Die Dispose-Methode wird in erster Linie implementiert, um nicht verwaltete Ressourcen freizugeben. Beim Arbeiten mit Instanzmembern, die IDisposable-Implementierungen sind, werden Dispose-Aufrufe häufig weitergegeben. Es gibt andere Gründe für die Implementierung von Dispose, z. B. um zugewiesenen Arbeitsspeicher freizugeben, ein Element zu entfernen, das einer Sammlung hinzugefügt wurde, oder um die Freigabe einer abgerufenen Sperre zu signalisieren.

Der .NET Garbage Collector weist keinen nicht verwalteten Speicher zu oder gibt diesen frei. Das Muster für das Verwerfen eines Objekts, Dispose-Muster genannt, legt die Ordnung für die Lebensdauer eines Objekts fest. Das Dispose-Muster wird für Objekte verwendet, die die IDisposable Schnittstelle implementieren. Dieses Muster tritt häufig bei der Interaktion mit Datei- und Pipehandles, Registrierungshandles, wait-Handles oder Zeigern auf Blöcke nicht verwalteten Arbeitsspeichers auf, da der Garbage Collector nicht verwaltete Objekte nicht wieder beanspruchen kann.

Damit sichergestellt ist, dass Ressourcen immer ordnungsgemäß bereinigt werden, sollte eine Dispose-Methode idempotent sein, was bedeutet, dass sie mehrmals aufgerufen werden kann, ohne eine Ausnahme auszulösen. Darüber hinaus sollten nachfolgende Aufrufe von Dispose nichts tun.

Das Codebeispiel für die GC.KeepAlive-Methode veranschaulicht, wie durch Garbage Collection die Ausführung eines Finalizers bewirkt werden kann, während ein nicht verwalteter Verweis auf das Objekt oder den Member weiterhin verwendet wird. Es kann sinnvoll sein, GC.KeepAlive zu verwenden, um das Objekt vom Beginn der aktuellen Routine bis zu dem Punkt, an dem diese Methode aufgerufen wird, für die Garbage Collection unzugänglich zu machen.

Tipp

In Bezug auf die Abhängigkeitsinjektion (DI) wird die Dienstlebensdauer beim Registrieren von Diensten in einer IServiceCollection implizit in Ihrem Namen verwaltet. Der IServiceProvider und die entsprechende IHost-Orchestrierungsressourcenbereinigung. Insbesondere werden Implementierungen von IDisposable und IAsyncDisposable am Ende ihrer angegebenen Lebensdauer ordnungsgemäß entsorgt.

Weitere Informationen finden Sie unter Abhängigkeitsinjektion in .NET.

Sichere Griffe

Das Schreiben von Code für den Finalizer eines Objekts ist eine komplexe Aufgabe, die Probleme verursachen kann, wenn sie nicht ordnungsgemäß ausgeführt werden. Daher wird empfohlen, System.Runtime.InteropServices.SafeHandle Objekte zu erstellen, anstatt einen Finalizer zu implementieren.

Ein System.Runtime.InteropServices.SafeHandle ist ein abstrakter verwalteter Typ, der eine System.IntPtr umschließt, die eine nicht verwaltete Ressource identifiziert. Unter Windows könnte es ein Handle bezeichnen, und unter UNIX einen Dateideskriptor. Die SafeHandle stellt alle Logik bereit, die erforderlich ist, um sicherzustellen, dass diese Ressource genau einmal freigegeben wird, entweder wenn der SafeHandle entsorgt wird oder wenn alle Verweise auf die SafeHandle gelöscht wurden und die SafeHandle Instanz finalisiert ist.

Die System.Runtime.InteropServices.SafeHandle ist eine abstrakte Basisklasse. Abgeleitete Klassen stellen bestimmte Instanzen für verschiedene Arten von Handles bereit. Diese abgeleiteten Klassen überprüfen, welche Werte für die System.IntPtr als ungültig betrachtet werden und wie sie das Handle tatsächlich freigeben. SafeFileHandle wird z. B. von SafeHandle abgeleitet, um IntPtrs zu umschließen, die geöffnete Dateihandles/Deskriptoren identifizieren, und überschreibt die SafeHandle.ReleaseHandle()-Methode, um sie zu schließen (über die close-Funktion unter UNIX oder CloseHandle-Funktion unter Windows). Die meisten APIs in .NET-Bibliotheken, die eine nicht verwaltete Ressource erstellen, umschließt sie in einer SafeHandle und gibt diese SafeHandle nach Bedarf an Sie zurück, anstatt den rohen Zeiger zurückzugeben. In Situationen, in denen Sie mit einer nicht verwalteten Komponente interagieren und eine IntPtr für eine nicht verwaltete Ressource abrufen, können Sie einen eigenen Typ SafeHandle erstellen, um sie einzuschließen. Daher müssen nur wenige Nicht-SafeHandle Typen Finalizer implementieren. Die meisten Implementierungen des Dispose-Musters schließen lediglich andere verwaltete Ressourcen ein, von denen einige SafeHandle-Objekte sein können.

Die folgenden abgeleiteten Klassen im Microsoft.Win32.SafeHandles-Namespace stellen sichere Handles bereit.

Klasse Ressourcen, die sie enthält
SafeFileHandle
SafeMemoryMappedFileHandle
SafePipeHandle
Dateien, im Arbeitsspeicher abgebildete Dateien und Pipes
SafeMemoryMappedViewHandle Speicheransichten
SafeNCryptKeyHandle
SafeNCryptProviderHandle
SafeNCryptSecretHandle
Kryptografiekonstrukte
SafeRegistryHandle Registrierungsschlüssel
SafeWaitHandle Wait-Handles

Dispose() und Dispose(bool)

Die IDisposable Schnittstelle erfordert die Implementierung einer einzelnen parameterlosen Methode, Dispose. Außerdem sollte jede nicht versiegelte Klasse über eine Dispose(bool)-Überladungsmethode verfügen.

Methodensignaturen sind:

  • public nicht virtuell (NotOverridable in Visual Basic) (IDisposable.Dispose-Implementierung).
  • protected virtual (Overridable in Visual Basic) Dispose(bool).

Die Dispose()-Methode

Da die public, nicht-virtuelle (NotOverridable in Visual Basic), parameterlose Dispose-Methode aufgerufen wird, wenn sie nicht mehr benötigt wird (von einem Benutzer des Typs), hat sie den Zweck, nicht verwaltete Ressourcen freizugeben, allgemeine Bereinigung durchzuführen und anzugeben, dass der Finalizer, falls vorhanden, nicht ausgeführt werden muss. Das Freigeben des tatsächlichen Arbeitsspeichers, der einem verwalteten Objekt zugeordnet ist, ist immer die Domäne des Garbage Collector. Aus diesem Gründen verfügt sie über eine Standardimplementierung:

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

Die Dispose-Methode führt die Bereinigung aller Objekte aus, damit der Garbage Collector nicht mehr die Object.Finalize-Überschreibung der Objekte aufrufen muss. Daher verhindert der Aufruf der SuppressFinalize-Methode, dass der Garbage Collector den Finalizer ausführt. Wenn der Typ keinen Finalizer besitzt, hat der Aufruf von GC.SuppressFinalize keine Auswirkungen. Die tatsächliche Bereinigung wird durch die Dispose(bool)-Methodenüberladung ausgeführt.

Die Überladung der Dispose(bool)-Methode

Bei der Überladung ist der disposing-Parameter ein Boolean, der angibt, ob der Methodenaufruf von einer Dispose-Methode (sein Wert ist true) oder von einem Finalizer (sein Wert ist false) kommt.

protected virtual void Dispose(bool disposing)
{
    if (_disposed)
    {
        return;
    }

    if (disposing)
    {
        // Dispose managed state (managed objects).
        // ...
    }

    // Free unmanaged resources.
    // ...

    _disposed = true;
}
Protected Overridable Sub Dispose(disposing As Boolean)
     If disposed Then Exit Sub

     If disposing Then

         ' Free managed resources.
         ' ...

     End If

     ' Free unmanaged resources.
     ' ...

     disposed = True
End Sub

Wichtig

Der parameter disposing sollte beim Aufruf eines Finalizers false werden, und true, wenn er von der IDisposable.Dispose-Methode aufgerufen wird. Mit anderen Worten, es ist true, wenn es deterministisch aufgerufen wird, und false, wenn es nicht deterministisch aufgerufen wird.

Der Textkörper der Methode besteht aus drei Codeblöcken:

  • Einem Block für die bedingte Rückgabe, wenn das Objekt bereits gelöscht wurde.

  • Ein bedingter Block, der verwaltete Ressourcen freigibt. Dieser Block wird ausgeführt, wenn der Wert von disposingtrueist. Die verwalteten Ressourcen, die sie freigibt, können Folgendes umfassen:

    • Verwaltete Objekte, die IDisposableimplementieren. Der bedingte Block kann verwendet werden, um deren Dispose-Implementierung aufzurufen (Weitergeben von Dispose-Aufrufen). Wenn Sie eine abgeleitete Klasse von System.Runtime.InteropServices.SafeHandle zum Umschließen Ihrer nicht verwalteten Ressource verwendet haben, sollten Sie hier die SafeHandle.Dispose() Implementierung aufrufen.
    • Verwaltete Objekte, die große Speichermengen verbrauchen oder knappe Ressourcen verbrauchen. Weisen Sie großen verwalteten Objektverweisen null zu, damit sie mit größerer Wahrscheinlichkeit nicht erreichbar sind. Dadurch werden sie schneller freigegeben, als wenn sie nicht auf deterministische Weise zurückgewonnen würden.
  • Ein Block, der nicht verwaltete Ressourcen freigibt. Dieser Block wird unabhängig vom Wert des disposing Parameters ausgeführt.

Wenn der Methodenaufruf von einem Finalizer stammt, sollte nur der Code ausgeführt werden, der nicht verwaltete Ressourcen freigibt. Der Implementierer ist dafür verantwortlich, sicherzustellen, dass der falsche Pfad nicht mit verwalteten Objekten interagiert, die möglicherweise verworfen wurden. Dies ist wichtig, da die Reihenfolge, in der der Garbage Collector verwaltete Objekte während der Fertigstellung entfernt, nicht deterministisch ist.

Weitergeben von Dispose-Aufrufen

Wenn Ihre Klasse ein Feld oder eine Eigenschaft besitzt und ihr Typ IDisposableimplementiert, sollte die enthaltende Klasse selbst auch IDisposableimplementieren. Eine Klasse, die eine IDisposable-Implementierung instanziiert und als Instanzmitglied speichert, ist auch für deren Bereinigung verantwortlich. Dadurch wird sichergestellt, dass die löschbaren Typen, auf die verwiesen wird, die Möglichkeit erhalten, eine Bereinigung deterministisch mit der Dispose-Methode auszuführen. Im folgenden Beispiel ist die Klasse sealed (oder 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

Tipp

  • Wenn Ihre Klasse über ein Feld oder eine Eigenschaft IDisposable verfügt, es/sie aber nicht besitzt, d. h., die Klasse erstellt das Objekt nicht, dann muss die Klasse IDisposable nicht implementieren.
  • Es gibt Fälle, in denen Sie eine null-Überprüfung in einem Finalizer durchführen möchten (einschließlich der Dispose(false)-Methode, die von einem Finalizer aufgerufen wird). Einer der Hauptgründe ist, wenn Sie nicht sicher sind, ob die Instanz vollständig initialisiert wurde (z. B. kann eine Ausnahme in einem Konstruktor ausgelöst werden).

Implementieren des Dispose-Musters

Alle nicht versiegelten Klassen (oder Visual Basic-Klassen, die nicht als NotInheritablegeändert wurden) sollten als potenzielle Basisklasse betrachtet werden, da sie geerbt werden können. Wenn Sie das Dispose-Muster für eine mögliche Basisklasse implementieren, müssen Sie Folgendes angeben:

  • Eine Dispose Implementierung, die die Dispose(bool)-Methode aufruft.
  • Eine Dispose(bool)-Methode, die die eigentliche Bereinigung ausführt.
  • Entweder eine von SafeHandle abgeleitete Klasse, die Ihre nicht verwaltete Ressource umschließt (empfohlen), oder eine Überschreibung der Object.Finalize-Methode. Die SafeHandle-Klasse bietet einen Finalizer, sodass Sie nicht selbst einen schreiben müssen.

Wichtig

Es ist möglich, dass eine Basisklasse nur auf verwaltete Objekte verweist und das Dispose-Muster implementiert. In diesen Fällen ist ein Finalizer unnötig. Ein Finalizer ist nur erforderlich, wenn Sie direkt auf nicht verwaltete Ressourcen verweisen.

Im Folgenden finden Sie ein allgemeines Beispiel für die Implementierung des Dispose-Musters für eine Basisklasse, die ein SafeHandle verwendet.

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

Anmerkung

Im vorherigen Beispiel wird ein SafeFileHandle-Objekt verwendet, um das Muster zu veranschaulichen; Stattdessen können alle von SafeHandle abgeleiteten Objekte verwendet werden. Beachten Sie, dass das Beispiel das SafeFileHandle-Objekt nicht ordnungsgemäß instanziieren kann.

Im Folgenden finden Sie das allgemeine Muster für die Implementierung des Dispose-Musters für eine Basisklasse, die Object.Finalize überschreibt.

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

Tipp

In C# implementieren Sie eine Finalisierung, indem Sie einen Finalizerbereitstellen, nicht durch Überschreiben von Object.Finalize. In Visual Basic erstellen Sie einen Finalizer mit Protected Overrides Sub Finalize().

Implementieren des Dispose-Musters für eine abgeleitete Klasse

Eine von einer Klasse abgeleitete Klasse, die die IDisposable-Schnittstelle implementiert, sollte nicht IDisposableimplementieren, da die Implementierung der Basisklasse von IDisposable.Dispose von den abgeleiteten Klassen geerbt wird. Um eine abgeleitete Klasse zu bereinigen, geben Sie stattdessen Folgendes an:

  • Eine protected override void Dispose(bool) Methode, die die Basisklassenmethode überschreibt und die tatsächliche Bereinigung der abgeleiteten Klasse durchführt. Diese Methode muss auch die base.Dispose(bool)-Methode (MyBase.Dispose(bool) in Visual Basic) aufrufen und ihr den Disposing-Status (bool disposing-Parameter) als Argument übergeben.
  • Entweder eine von SafeHandle abgeleitete Klasse, die die nicht verwaltete Ressource einschließt (empfohlen) oder eine Überschreibung der Object.Finalize-Methode. Die SafeHandle-Klasse stellt einen Finalizer bereit, der Ihnen erspart, einen zu schreiben. Wenn Sie einen Finalizer bereitstellen, muss er die Dispose(bool)-Überladung mit dem false-Argument aufrufen.

Im Folgenden finden Sie ein Beispiel für das allgemeine Muster zur Implementierung des Dispose-Musters für eine abgeleitete Klasse, die ein sicheres Handle verwendet:

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

Anmerkung

Im vorherigen Beispiel wird ein SafeFileHandle-Objekt verwendet, um das Muster zu veranschaulichen; Stattdessen können alle von SafeHandle abgeleiteten Objekte verwendet werden. Beachten Sie, dass das Beispiel das SafeFileHandle-Objekt nicht ordnungsgemäß instanziieren kann.

Im Folgenden finden Sie das allgemeine Muster für das Implementieren des Dispose-Musters für eine abgeleitete Klasse, die Object.Finalize überschreibt:

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

Siehe auch