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
disposing
true
ist. 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 derDispose(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 NotInheritable
geä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 diebase.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 demfalse
-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