Implementacja metody Dispose
Metoda Dispose jest implementowana głównie w celu wydania niezarządzanych zasobów. Podczas pracy z elementami członkowskimi wystąpień, które są IDisposable implementacjami, często są wywoływane Dispose kaskadowo. Istnieją inne przyczyny implementacji Disposeprogramu , na przykład w celu zwolnienia pamięci przydzielonej, usunięcia elementu dodanego do kolekcji lub zasygnalizowania zwolnienia uzyskanej blokady.
Moduł odśmiecanie pamięci platformy .NET nie przydziela ani nie zwalnia niezarządzanej pamięci. Wzorzec usuwania obiektu, określany jako wzorzec usuwania, nakłada kolejność na okres istnienia obiektu. Wzorzec usuwania jest używany dla obiektów implementujących IDisposable interfejs. Ten wzorzec jest typowy podczas interakcji z dojściami plików i potoków, dojściami rejestru, uchwytami oczekiwania lub wskaźnikami do bloków niezarządzanej pamięci, ponieważ moduł odśmiecania pamięci nie może odzyskać niezarządzanych obiektów.
Aby zapewnić, że zasoby są zawsze prawidłowo czyszczone, Dispose metoda powinna być idempotentna, tak aby można było ją wywołać wielokrotnie bez zgłaszania wyjątku. Ponadto kolejne wywołania Dispose nie powinny nic robić.
W przykładzie kodu podanym GC.KeepAlive dla metody pokazano, jak odzyskiwanie pamięci może spowodować uruchomienie finalizatora, gdy niezarządzane odwołanie do obiektu lub jego składowych jest nadal używane. Może to być przydatne, aby GC.KeepAlive obiekt był niekwalifikowany do odzyskiwania pamięci od początku bieżącej procedury do punktu, w którym ta metoda jest wywoływana.
Napiwek
W odniesieniu do wstrzykiwania zależności podczas rejestrowania usług w programie IServiceCollectionokres istnienia usługi jest zarządzany niejawnie w Twoim imieniu. I IServiceProvider odpowiednie IHost organizowanie oczyszczania zasobów. W szczególności implementacje IDisposable i IAsyncDisposable są prawidłowo usuwane na koniec określonego okresu istnienia.
Aby uzyskać więcej informacji, zobacz Wstrzykiwanie zależności na platformie .NET.
dojścia Sejf
Pisanie kodu dla finalizatora obiektu to złożone zadanie, które może powodować problemy, jeśli nie zostanie wykonane prawidłowo. Dlatego zalecamy konstruowanie System.Runtime.InteropServices.SafeHandle obiektów zamiast implementowania finalizatora.
A System.Runtime.InteropServices.SafeHandle jest abstrakcyjnym typem zarządzanym, który opakowuje zasób System.IntPtr niezarządzany. W systemie Windows może zidentyfikować uchwyt, a w systemie Unix deskryptor plików. Element SafeHandle
zapewnia całą logikę niezbędną do zapewnienia, że ten zasób jest zwalniany raz i tylko raz, w przypadku SafeHandle
usunięcia lub usunięcia wszystkich odwołań do SafeHandle
obiektu i SafeHandle
sfinalizowania wystąpienia.
Jest System.Runtime.InteropServices.SafeHandle to abstrakcyjna klasa bazowa. Klasy pochodne zapewniają określone wystąpienia dla różnych rodzajów uchwytów. Te klasy pochodne sprawdzają, jakie wartości są System.IntPtr uznawane za nieprawidłowe i jak rzeczywiście zwolnić uchwyt. Na przykład SafeFileHandle pochodzi z SafeHandle
elementu , aby opakowywać IntPtrs
, który identyfikuje otwarte uchwyty/deskryptory plików i zastępuje jego SafeHandle.ReleaseHandle() metodę w celu jego zamknięcia (za pośrednictwem close
funkcji w systemie Unix lub CloseHandle
funkcji w systemie Windows). Większość interfejsów API w bibliotekach platformy .NET, które tworzą niezarządzany zasób, opakowuje go w SafeHandle
obiekcie i zwraca je SafeHandle
zgodnie z potrzebami, a nie przekazując z powrotem nieprzetworzone wskaźniki. W sytuacjach, w których wchodzisz w interakcję ze składnikiem niezarządzanym i pobierasz zasób IntPtr
niezarządzany, możesz utworzyć własny SafeHandle
typ, aby go opakowować. W związku z tym kilka typów innych niżSafeHandle
muszą implementować finalizatory. Większość jednorazowych implementacji wzorców kończy się tylko zawijaniem innych zasobów zarządzanych, z których niektóre mogą być SafeHandle
obiektami.
Następujące klasy pochodne w Microsoft.Win32.SafeHandles przestrzeni nazw zapewniają bezpieczne dojścia.
Klasa | Przechowywane zasoby |
---|---|
SafeFileHandle SafeMemoryMappedFileHandle SafePipeHandle |
Pliki, pliki mapowane w pamięci i potoki |
SafeMemoryMappedViewHandle | Widoki pamięci |
SafeNCryptKeyHandle SafeNCryptProviderHandle SafeNCryptSecretHandle |
Konstrukcje kryptograficzne |
SafeRegistryHandle | Klucze rejestru |
SafeWaitHandle | Uchwyty oczekiwania |
Dispose() i Dispose(bool)
Interfejs IDisposable wymaga implementacji pojedynczej metody bez parametrów, Dispose. Ponadto każda klasa bez zapieczętowania powinna mieć metodę Dispose(bool)
przeciążenia.
Podpisy metod to:
public
non-virtual (NotOverridable
w Visual Basic) (IDisposable.Dispose implementacja).protected virtual
(Overridable
w Visual Basic)Dispose(bool)
.
Metoda Dispose()
public
Ponieważ metoda , niewirtualna (NotOverridable
w Visual Basic), metoda bez Dispose
parametrów jest wywoływana, gdy nie jest już potrzebna (przez konsumenta typu), jej celem jest zwolnienie niezarządzanych zasobów, przeprowadzenie ogólnego czyszczenia i wskazanie, że finalizator, jeśli taki jest obecny, nie musi działać. Zwalnianie rzeczywistej pamięci skojarzonej z obiektem zarządzanym jest zawsze domeną modułu odśmiecania pamięci. Z tego powodu ma standardową implementację:
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
Metoda Dispose
wykonuje oczyszczanie wszystkich obiektów, więc moduł odśmieceń pamięci nie musi już wywoływać zastąpienia obiektów Object.Finalize . W związku z tym wywołanie SuppressFinalize metody uniemożliwia uruchomienie finalizatora modułu odśmiecającego śmieci. Jeśli typ nie ma finalizatora, wywołanie GC.SuppressFinalize metody nie ma żadnego efektu. Rzeczywiste czyszczenie jest wykonywane przez Dispose(bool)
przeciążenie metody.
Przeciążenie metody Dispose(bool)
W przeciążeniu parametr jest parametrem wskazującym, disposing
czy wywołanie metody pochodzi z Dispose metody (jej wartość to true
) czy z finalizatora (jego wartość to false
).Boolean
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
Ważne
Parametr disposing
powinien być false
wywoływany z finalizatora i true
wywoływany IDisposable.Dispose z metody . Innymi słowy, jest true
to, gdy deterministycznie nazywane i false
gdy niedeterministyczne.
Treść metody składa się z trzech bloków kodu:
Blok powrotu warunkowego, jeśli obiekt jest już usunięty.
Blok zwalniający niezarządzane zasoby. Ten blok jest wykonywany niezależnie od wartości parametru
disposing
.Blok warunkowy zwalniający zarządzane zasoby. Ten blok jest wykonywany, jeśli wartość
disposing
totrue
. Zarządzane zasoby, które zwalnia, to m.in.:Zarządzane obiekty, które implementują IDisposableelement . Blok warunkowy może służyć do wywoływania ich Dispose implementacji (kaskadowego usuwania). Jeśli użyto klasy pochodnej System.Runtime.InteropServices.SafeHandle do opakowania niezarządzanego zasobu, należy wywołać implementację SafeHandle.Dispose() tutaj.
Obiekty zarządzane, które zużywają duże ilości pamięci lub zużywają ograniczone zasoby. Przypisz odwołania do dużych obiektów zarządzanych, aby
null
były bardziej prawdopodobne, aby były niedostępne. Spowoduje to ich szybsze wydanie niż w przypadku ich odzyskania nieokreślono.
Jeśli wywołanie metody pochodzi z finalizatora, należy wykonać tylko kod, który zwalnia niezarządzane zasoby. Implementator jest odpowiedzialny za zapewnienie, że ścieżka false nie wchodzi w interakcje z zarządzanymi obiektami, które mogły zostać usunięte. Jest to ważne, ponieważ kolejność usuwania obiektów zarządzanych przez moduł odśmiecania pamięci podczas finalizacji jest nieokreślona.
Wywołania usuwania kaskadowego
Jeśli klasa jest właścicielem pola lub właściwości, a jej typ implementuje IDisposableelement , element zawierający klasę powinien również implementować IDisposableelement . Klasa, która tworzy wystąpienie implementacji IDisposable i przechowuje ją jako element członkowski wystąpienia, jest również odpowiedzialna za jego oczyszczanie. Pomaga to zagwarantować, że przywoływane typy jednorazowe mają możliwość deterministycznego przeprowadzania Dispose oczyszczania za pomocą metody . W poniższym przykładzie klasa to sealed
(lub NotInheritable
w 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
Napiwek
- Jeśli klasa ma pole lub właściwość, ale nie jest jej właścicielemIDisposable, co oznacza, że klasa nie tworzy obiektu, klasa nie musi implementować IDisposableklasy .
- Istnieją przypadki, w których można wykonać
null
-sprawdzanie w finalizatorze (który obejmujeDispose(false)
metodę wywoływaną przez finalizator). Jedną z głównych przyczyn jest to, że nie masz pewności, czy wystąpienie zostało w pełni zainicjowane (na przykład wyjątek może zostać zgłoszony w konstruktorze).
Implementowanie wzorca usuwania
Wszystkie nieszczelnie zapieczętowane klasy (lub klasy Visual Basic, które nie zostały zmodyfikowane jako NotInheritable
) powinny być traktowane jako potencjalna klasa bazowa, ponieważ mogą być dziedziczone. W przypadku zaimplementowania wzorca usuwania dla dowolnej potencjalnej klasy bazowej należy podać następujące elementy:
- Implementacja Dispose , która wywołuje metodę
Dispose(bool)
. - Metoda
Dispose(bool)
, która wykonuje rzeczywiste oczyszczanie. - Klasa pochodząca z SafeHandle tej klasy opakowuje niezarządzany zasób (zalecany) lub zastąpi metodę Object.Finalize . Klasa SafeHandle udostępnia finalizator, więc nie musisz pisać go samodzielnie.
Ważne
Istnieje możliwość, aby klasa bazowa odwoływać się tylko do obiektów zarządzanych i implementować wzorzec usuwania. W takich przypadkach finalizator jest niepotrzebny. Finalizator jest wymagany tylko wtedy, gdy bezpośrednio odwołujesz się do zasobów niezarządzanych.
Oto ogólny przykład implementacji wzorca usuwania dla klasy bazowej, która używa bezpiecznego uchwytu.
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
Uwaga
W poprzednim przykładzie użyto SafeFileHandle obiektu do zilustrowania wzorca. Zamiast tego można użyć dowolnego obiektu pochodzącego z SafeHandle metody . Zwróć uwagę, że w przykładzie nie jest poprawnie tworzone wystąpienie obiektu SafeFileHandle .
Oto ogólny wzorzec implementowania wzorca usuwania dla klasy bazowej, która zastępuje Object.Finalizeelement .
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
Napiwek
W języku C# implementujesz finalizację, podając finalizator, a nie przez zastąpienie elementu Object.Finalize. W języku Visual Basic utworzysz finalizator za pomocą polecenia Protected Overrides Sub Finalize()
.
Implementowanie wzorca usuwania dla klasy pochodnej
Klasa pochodząca z klasy, która implementuje IDisposable interfejs, nie powinna implementować IDisposableklasy , ponieważ implementacja klasy bazowej IDisposable.Dispose klasy jest dziedziczona przez jej klasy pochodne. Zamiast tego, aby wyczyścić klasę pochodną, należy podać następujące informacje:
protected override void Dispose(bool)
Metoda, która zastępuje metodę klasy bazowej i wykonuje rzeczywiste czyszczenie klasy pochodnej. Ta metoda musi również wywołać metodębase.Dispose(bool)
(MyBase.Dispose(bool)
w Visual Basic), przekazując ją jakobool disposing
argument .- Klasa pochodząca z SafeHandle tej klasy opakowuje niezarządzany zasób (zalecany) lub zastąpi metodę Object.Finalize . Klasa SafeHandle udostępnia finalizator, który uwalnia cię od konieczności kodowania. Jeśli podasz finalizator, musi wywołać
Dispose(bool)
przeciążenie z argumentemfalse
.
Oto przykład ogólnego wzorca implementowania wzorca usuwania dla klasy pochodnej korzystającej z bezpiecznego uchwytu:
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
Uwaga
W poprzednim przykładzie użyto SafeFileHandle obiektu do zilustrowania wzorca. Zamiast tego można użyć dowolnego obiektu pochodzącego z SafeHandle metody . Zwróć uwagę, że w przykładzie nie jest poprawnie tworzone wystąpienie obiektu SafeFileHandle .
Oto ogólny wzorzec implementowania wzorca usuwania dla klasy pochodnej, która zastępuje Object.Finalize:
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