Een verwijderingsmethode implementeren
De methode Dispose wordt voornamelijk geïmplementeerd om onbeheerde resources vrij te geven. Wanneer u werkt met exemplaarleden die zijn IDisposable implementaties, is het gebruikelijk om Dispose aanroepen trapsgewijs te Dispose. Er zijn andere redenen om Disposete implementeren, bijvoorbeeld om geheugen vrij te maken dat is toegewezen, een item te verwijderen dat aan een verzameling is toegevoegd of om de release van een vergrendeling te signaleren die is verkregen.
De .NET garbage collector wijst geen onbeheerd geheugen toe of laat het los. Het patroon voor het verwijderen van een object, ook wel het verwijderingspatroon genoemd, legt orde op voor de levensduur van een object. Het dispose-patroon wordt gebruikt voor objecten die de IDisposable-interface implementeren. Dit patroon is gebruikelijk bij interactie met bestands- en pijpgrepen, registergrepen, wachtgrepen of aanwijzers naar blokken onbeheerd geheugen, omdat de garbagecollector niet in staat is onbeheerde objecten vrij te maken.
Om ervoor te zorgen dat resources altijd correct worden opgeschoond, moet een Dispose methode idempotent zijn, zodat deze meerdere keren kan worden aangeroepen zonder een uitzondering te genereren. Verder mogen volgende aanroepen van Dispose niets doen.
In het codevoorbeeld voor de GC.KeepAlive methode ziet u hoe garbagecollection kan leiden tot uitvoering van een finalizer terwijl een niet-beheerde verwijzing naar het object of de leden ervan nog in gebruik is. Het kan zinvol zijn om GC.KeepAlive te gebruiken om het object niet in aanmerking te laten komen voor garbagecollection vanaf het begin van de huidige routine tot het punt waar deze methode wordt aangeroepen.
Tip
Wat afhankelijkheidsinjectie betreft, wordt bij het registreren van services in een IServiceCollectionde levensduur van de service impliciet namens u beheerd. De IServiceProvider en de bijbehorende IHost coördineren het opschonen van middelen. Met name worden implementaties van IDisposable en IAsyncDisposable aan het einde van hun opgegeven levensduur correct verwijderd.
Zie Afhankelijkheidsinjectie in .NETvoor meer informatie.
Veilige ingangen
Het schrijven van code voor de finalizer van een object is een complexe taak die problemen kan veroorzaken als deze niet correct worden uitgevoerd. Daarom raden we u aan System.Runtime.InteropServices.SafeHandle objecten te maken in plaats van een finalizer te implementeren.
Een System.Runtime.InteropServices.SafeHandle is een abstract beheerd type dat een System.IntPtr verpakt die een onbeheerde resource identificeert. In Windows kan het een ingang identificeren en op Unix, een bestandsdescriptor. De SafeHandle
biedt alle logica die nodig is om ervoor te zorgen dat deze resource eenmaal en slechts één keer wordt vrijgegeven, hetzij wanneer de SafeHandle
wordt verwijderd of wanneer alle verwijzingen naar de SafeHandle
zijn verwijderd en het SafeHandle
exemplaar is voltooid.
De System.Runtime.InteropServices.SafeHandle is een abstracte basisklasse. Afgeleide klassen bieden specifieke exemplaren voor verschillende soorten handgrepen. Deze afgeleide klassen valideren welke waarden voor de System.IntPtr als ongeldig worden beschouwd en hoe de handle daadwerkelijk vrijgemaakt wordt. Bijvoorbeeld, SafeFileHandle is afgeleid van SafeHandle
om IntPtrs
te verpakken, die geopende bestandsdescriptors identificeren, en het overschrijft de SafeHandle.ReleaseHandle()-methode om deze te sluiten (via de close
-functie op Unix of de CloseHandle
-functie op Windows). De meeste API's in .NET-bibliotheken die een onbeheerde resource aanmaken, verpakken het in een SafeHandle
en geven die SafeHandle
indien nodig terug, in plaats van de onbewerkte aanwijzer terug te geven. In situaties waarin u communiceert met een niet-beheerd onderdeel en een IntPtr
voor een onbeheerde resource ontvangt, kunt u uw eigen SafeHandle
type maken om het te verpakken. Als gevolg hiervan hoeven weinig niet-SafeHandle
typen finalizers te implementeren. De meeste implementaties van wegwerppatronen komen erop neer dat ze andere beheerde middelen verpakken, waarvan sommige mogelijk SafeHandle
-objecten zijn.
De volgende afgeleide klassen in de Microsoft.Win32.SafeHandles naamruimte bieden veilige ingangen.
Klas | Resources die het bezit |
---|---|
SafeFileHandle SafeMemoryMappedFileHandle SafePipeHandle |
Bestanden, geheugengekoppelde bestanden en pijpleidingen |
SafeMemoryMappedViewHandle | Geheugenweergaven |
SafeNCryptKeyHandle SafeNCryptProviderHandle SafeNCryptSecretHandle |
Cryptografieconstructies |
SafeRegistryHandle | Registersleutels |
SafeWaitHandle | Wachtgrepen |
Dispose() and Dispose(bool)
De IDisposable-interface vereist de implementatie van één methode zonder parameters, Dispose. Bovendien moet elke niet-verzegelde klasse een Dispose(bool)
overloadmethode hebben.
Methodesignaturen zijn:
-
public
niet-virtueel (NotOverridable
in Visual Basic) (IDisposable.Dispose implementatie). -
protected virtual
(Overridable
in Visual Basic)Dispose(bool)
.
De methode Dispose()
Omdat de public
, niet-virtueel (NotOverridable
in Visual Basic), parameterloze Dispose
methode wordt aangeroepen wanneer deze niet meer nodig is (door een consument van het type), is het doel ervan om onbeheerde resources vrij te maken, algemene opschoning uit te voeren en aan te geven dat de finalizer, indien aanwezig, niet hoeft te worden uitgevoerd. Het vrijmaken van het werkelijke-geheugen dat is gekoppeld aan een beheerd-object is altijd het domein van de garbagecollector. Daarom heeft het een standaard implementatie:
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
De Dispose
-methode voert alle opruimwerkzaamheden voor objecten uit, zodat de garbagecollector de overschrijving van de objecten' Object.Finalize niet meer hoeft aan te roepen. Daarom voorkomt de aanroep van de SuppressFinalize methode dat de garbagecollector de finalizer uitvoert. Als het type geen finalizer heeft, heeft de aanroep naar GC.SuppressFinalize geen effect. De werkelijke opschoning wordt uitgevoerd door de Dispose(bool)
methode overbelasting.
De overbelasting van de methode Dispose(bool)
In de overbelasting is de parameter disposing
een Boolean die aangeeft of de methode-aanroep afkomstig is van een Dispose methode (de waarde is true
) of van een finalizer (de waarde is false
).
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
Belangrijk
De parameter disposing
moet worden false
wanneer deze wordt aangeroepen vanuit een finalizer en true
wanneer deze wordt aangeroepen vanuit de methode IDisposable.Dispose. Met andere woorden, het is true
wanneer deterministisch aangeroepen en false
wanneer niet-deterministisch wordt aangeroepen.
De hoofdtekst van de methode bestaat uit drie codeblokken:
Een blok voor voorwaardelijk retourneren als het object al is verwijderd.
Een voorwaardelijk blok waarmee beheerde resources worden vrijgemaakt. Dit blok wordt uitgevoerd als de waarde van
disposing
istrue
. De beheerde resources die worden vrijgemaakt, kunnen het volgende omvatten:- Beheerde objecten die IDisposableimplementeren. Het voorwaardelijke blok kan worden gebruikt om hun Dispose implementatie aan te roepen (trapsgewijs verwijderen). Als u een afgeleide klasse van System.Runtime.InteropServices.SafeHandle hebt gebruikt om uw onbeheerde resource te verpakken, moet u de SafeHandle.Dispose() implementatie hier aanroepen.
- Beheerde objecten die grote hoeveelheden geheugen verbruiken of schaarse resources verbruiken. Wijs grote beheerde objectverwijzingen toe aan
null
om ze waarschijnlijk onbereikbaar te maken. Dit brengt ze sneller vrij dan als ze niet-deterministisch werden vrijgemaakt.
Een blok waarmee onbeheerde resources worden vrijgemaakt. Dit blok wordt uitgevoerd, ongeacht de waarde van de parameter
disposing
.
Als de methode-aanroep afkomstig is van een finalizer, moet alleen de code worden uitgevoerd die onbeheerde resources vrijgeeft. De implementator is verantwoordelijk voor het verzekeren dat het vals pad niet omgaat met beheerde objecten die mogelijk zijn verwijderd. Dit is belangrijk omdat de volgorde waarin de garbagecollector beheerde objecten verwijdert tijdens het voltooien niet-deterministisch is.
Oproepen cascaderend afhandelen
Als uw klasse eigenaar is van een veld of eigenschap en het bijbehorende type IDisposableimplementeert, moet de betreffende klasse zelf ook IDisposableimplementeren. Een klasse die een IDisposable-implementatie instantieert en opslaat als exemplaarlid, is ook verantwoordelijk voor het opschonen ervan. Dit helpt ervoor te zorgen dat de genoemde wegwerptypen de kans krijgen om via de Dispose-methode deterministisch op te ruimen. In het volgende voorbeeld is de klasse sealed
(of 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
Tip
- Als uw klasse een IDisposable veld of eigenschap heeft, maar niet eigen, wat betekent dat de klasse het object niet maakt, hoeft de klasse geen IDisposablete implementeren.
- Er zijn gevallen waarin u
null
-check in een finalizer wilt uitvoeren (inclusief deDispose(false)
methode die wordt aangeroepen door een finalizer). Een van de belangrijkste redenen is dat u niet zeker weet of de instantie volledig is geïnitialiseerd (er kan bijvoorbeeld een uitzondering in een constructor worden geworpen).
Het verwijderingspatroon implementeren
Alle niet-verzegelde klassen (of Visual Basic-klassen die niet zijn gewijzigd als NotInheritable
) moeten worden beschouwd als een mogelijke basisklasse, omdat ze kunnen worden overgenomen. Als u het verwijderingspatroon implementeert voor een mogelijke basisklasse, moet u het volgende opgeven:
- Een Dispose-implementatie die de methode
Dispose(bool)
aanroept. - Een
Dispose(bool)
methode waarmee de werkelijke opschoning wordt uitgevoerd. - Een klasse die is afgeleid van SafeHandle die uw onbeheerde resource verpakt (aanbevolen) of een overschrijving naar de Object.Finalize methode. De SafeHandle-klasse biedt een finalizer, zodat u er zelf geen hoeft te schrijven.
Belangrijk
Het is mogelijk dat een basisklasse alleen verwijst naar beheerde objecten en het verwijderingspatroon implementeert. In deze gevallen is een finalizer niet nodig. Een finalizer is alleen vereist als u rechtstreeks naar onbeheerde resources verwijst.
Hier is een algemeen voorbeeld van de implementatie van het dispose-patroon voor een basisklasse die gebruikmaakt van een veilig handvat.
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
Notitie
In het vorige voorbeeld wordt een SafeFileHandle object gebruikt om het patroon te illustreren; elk object dat is afgeleid van SafeHandle kan in plaats daarvan worden gebruikt. Let op dat in het voorbeeld het SafeFileHandle-object niet correct wordt geïnitialiseerd.
Dit is het algemene patroon voor het implementeren van het dispose-patroon voor een basisklasse die Object.Finalizeoverschrijft.
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
Fooi
In C# implementeert u een finalisatie door een finalizer op te geven, niet door een override van Object.Finalizeuit te voeren. In Visual Basic maakt u een finalizer met Protected Overrides Sub Finalize()
.
Het verwijderingspatroon voor een afgeleide klasse implementeren
Een klasse die is afgeleid van een klasse die de IDisposable-interface implementeert, mag geen IDisposableimplementeren, omdat de basisklasse-implementatie van IDisposable.Dispose wordt overgenomen door de afgeleide klassen. Als u een afgeleide klasse wilt opschonen, geeft u het volgende op:
- Een
protected override void Dispose(bool)
methode die de basisklassemethode overschrijft en de werkelijke opschoonactie van de afgeleide klasse uitvoert. Met deze methode moet ook de methodebase.Dispose(bool)
(MyBase.Dispose(bool)
in Visual Basic) worden aangeroepen die de disponerende status (bool disposing
parameter) doorgeeft als argument. - Een klasse die is afgeleid van SafeHandle, die uw onbeheerde resource omhult (aanbevolen), of een overschrijving van de Object.Finalize-methode. De SafeHandle-klasse biedt een finalizer waarmee u geen code hoeft uit te voeren. Als u een finalizer opgeeft, moet deze de
Dispose(bool)
overload-functie aanroepen met argumentfalse
.
Hier volgt een voorbeeld van het algemene patroon voor het implementeren van het verwijderingspatroon voor een afgeleide klasse die gebruikmaakt van een veilige ingang:
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
Notitie
In het vorige voorbeeld wordt een SafeFileHandle object gebruikt om het patroon te illustreren; elk object dat is afgeleid van SafeHandle kan in plaats daarvan worden gebruikt. In het voorbeeld wordt het SafeFileHandle-object niet correct geïnstantieerd.
Dit is het algemene patroon voor het implementeren van het verwijderingspatroon voor een afgeleide klasse die Object.Finalizeoverschrijft:
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