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 weitere Gründe für die Implementierung von Dispose, wie beispielsweise das Freigeben von zugeordnetem Arbeitsspeicher, das Entfernen eines Elements, das einer Auflistung hinzugefügt wurde, oder das Signalisieren der Aufhebung einer abgerufenen Sperre.

Der .NET-Garbage Collector ordnet nicht verwalteten Arbeitsspeicher weder zu noch gibt er 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.

Um sicherzustellen, dass Ressourcen immer entsprechend bereinigt werden, sollte eine Dispose-Methode derart idempotent sein, dass sie mehrmals aufgerufen werden kann, ohne eine Ausnahme auszulösen. Außerdem sollten nachfolgende Aufrufe von Dispose nichts bewirken.

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. Sie sollten GC.KeepAlive verwenden, damit das Objekt von Beginn der aktuellen Routine bis zum Zeitpunkt des Aufrufs dieser Methode von der Garbage Collection ausgenommen wird.

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 Implementierungen von IDisposable und IAsyncDisposable werden am Ende ihrer angegebenen Lebensdauer ordnungsgemäß gelöscht.

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

Sichere Handles

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

Bei einem System.Runtime.InteropServices.SafeHandle handelt es sich um einen abstrakten verwalteten Typ, der einen System.IntPtr umschließt, der eine nicht verwaltete Ressource identifiziert. Unter Windows könnte es ein Handle bezeichnen, und unter UNIX einen Dateideskriptor. Das SafeHandle bietet die gesamte erforderliche Logik, um sicherzustellen, dass diese Ressource einmalig (und wirklich nur einmal) freigegeben wird, wenn das SafeHandle gelöscht wird, oder wenn alle Verweise auf das SafeHandle gelöscht wurden und die SafeHandle-Instanz finalisiert wurde.

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 den System.IntPtr als ungültig angesehen werden, und wie das Handle tatsächlich freigegeben werden soll. 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ßen sie in einem SafeHandle und geben dieses SafeHandle bei Bedarf zurück, anstatt den Rohzeiger zu übergeben. In Situationen, in denen Sie mit einer nicht verwalteten Komponente interagieren und einen IntPtr für eine nicht verwaltete Ressource erhalten, können Sie einen eigenen SafeHandle-Typ erstellen, um sie zu umschließen. Daraus ergibt sich, dass nur wenige Nicht-SafeHandle-Typen Finalizer implementieren müssen. 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 Darin enthaltene Ressourcen
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 (von einem Consumer des Typs) aufgerufen wird, wenn sie nicht mehr benötigt wird, hat sie den Zweck, nicht verwaltete Ressourcen freizugeben, eine generelle Bereinigung durchzuführen und anzuzeigen, dass der Finalizer, sofern vorhanden, nicht ausgeführt werden muss. Das Freigeben des tatsächlichen Speichers, der einem verwalteten Objekt zugeordnet ist, ist immer die Domäne des Garbage Collectors. Daher weist sie eine Standardimplementierung auf:

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 aufweist, bleibt der Aufruf von GC.SuppressFinalize ohne Auswirkungen. Die tatsächliche Bereinigung wird durch die Dispose(bool)-Methodenüberladung ausgeführt.

Die Dispose(bool)-Methodenüberladung

In der Überladung ist der disposing-Parameter ein Boolean-Wert, der angibt, ob der Methodenaufruf von einer Dispose-Methode (deren Wert true ist) oder einem Finalizer (dessen Wert false ist) stammt.

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

Wichtig

Der disposing-Parameter sollte false sein, wenn er von einem Finalizer aufgerufen wird, und true, wenn er von der IDisposable.Dispose-Methode aufgerufen wird. Dies bedeutet, dass er true ist, wenn er deterministisch aufgerufen wird, und false, wenn er nicht deterministisch aufgerufen wird.

Der Text der Methode besteht aus drei Codeblöcken:

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

  • Ein Block, der nicht verwaltete Ressourcen freigibt. Dieser Block wird unabhängig vom Wert des disposing-Parameters ausgeführt.

  • Ein bedingter Block, der verwaltete Ressourcen freigibt. Dieser Block wird ausgeführt, wenn der Wert von disposing gleich true ist. Die verwalteten Ressourcen, die freigegeben werden, können Folgendes umfassen:

    • Verwaltete Objekte, die IDisposable implementieren. 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 verwendet haben, um die nicht verwaltete Ressource zu umschließen, sollten Sie hier die SafeHandle.Dispose()-Implementierung aufrufen.

    • Verwaltete Objekte, die viel Arbeitsspeicher belegen oder knappe Ressourcen nutzen. Weisen Sie null Verweise auf große verwaltete Objekte zu, damit sie eher unerreichbar sind. So werden sie schneller freigegeben, als wenn sie nicht-deterministisch freigegeben würden.

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

Weitergeben von Dispose-Aufrufen

Wenn Ihre Klasse ein Feld oder eine Eigenschaft besitzt und dessen/deren Typ IDisposable implementiert, sollte die enthaltende Klasse auch IDisposable implementieren. Eine Klasse, die eine IDisposable-Implementierung instanziiert und als Instanz-Member speichert, ist auch für die 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. könnte eine Ausnahme in einem Konstruktor ausgelöst werden).

Implementieren des Dispose-Musters

Alle nicht versiegelten Klassen (oder nicht als NotInheritable geänderten Visual Basic-Klassen) sollten als potenzielle Basisklasse angesehen werden, da sie geerbt werden könnten. Wenn Sie das Dispose-Muster für eine potenzielle Basisklasse implementieren, müssen Sie Folgendes bereitstellen:

  • 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 die nicht verwaltete Ressource einschließt (empfohlen) oder eine Überschreibung der Object.Finalize-Methode. Die SafeHandle-Klasse stellt einen Finalizer bereit, sodass Sie keinen selbst 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

Hinweis

Im vorherigen Beispiel wird ein SafeFileHandle-Objekt zum Veranschaulichen des Musters verwendet; stattdessen kann auch ein beliebiges anderes von SafeHandle abgeleitetes Objekt verwendet werden. Beachten Sie, dass im Beispiel das SafeFileHandle-Objekt nicht ordnungsgemäß instanziiert wird.

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 Finalizer bereitstellen, nicht durch Außerkraftsetzen 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 Klasse, die von einer Klasse abgeleitet ist, die die IDisposable-Schnittstelle implementiert, sollte IDisposable nicht implementieren, da die Basisklassenimplementierung von IDisposable.Dispose von den abgeleiteten Klassen geerbt wird. Stellen Sie zum Bereinigen einer abgeleiteten Klasse stattdessen Folgendes bereit:

  • Eine protected override void Dispose(bool)-Methode, die die Basisklassenmethode überschreibt und die eigentliche 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, wodurch Sie keinen programmieren müssen. 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

Hinweis

Im vorherigen Beispiel wird ein SafeFileHandle-Objekt zum Veranschaulichen des Musters verwendet; stattdessen kann auch ein beliebiges anderes von SafeHandle abgeleitetes Objekt verwendet werden. Beachten Sie, dass im Beispiel das SafeFileHandle-Objekt nicht ordnungsgemäß instanziiert wird.

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