Implementowanie metody Dispose
Metoda Dispose jest przede wszystkim wdrażana w celu zwolnienia niezarządzanych zasobów. Podczas pracy z elementami członkowskimi wystąpienia, będącymi implementacjami IDisposable, często wywołuje się kaskadowo Dispose. Istnieją inne przyczyny implementacji Dispose, na przykład w celu zwolnienia pamięci przydzielonej, usunięcia elementu dodanego do kolekcji lub zasygnalizowania zwolnienia uzyskanej blokady.
moduł odśmiecania 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 interfejs IDisposable. Ten schemat jest typowy podczas interakcji z uchwytami plików i potoków, uchwytami rejestru, uchwytami oczekiwania lub wskaźnikami do bloków niezarządzanej pamięci, ponieważ kolektor pamięci nie może odzyskać niezarządzanych obiektów.
Aby zapewnić, że zasoby są zawsze prawidłowo czyszczone, metoda Dispose powinna być idempotentna, by można ją było wywołać wielokrotnie bez generowania wyjątku. Ponadto kolejne wywołania Dispose nie powinny nic robić.
Przykładowy kod podany dla metody GC.KeepAlive pokazuje, jak odzyskiwanie pamięci może spowodować uruchomienie finalizatora, gdy niezarządzane odwołanie do obiektu lub jego składowych jest nadal używane. Warto wykorzystać GC.KeepAlive, aby obiekt był niepoddawany odzyskiwaniu pamięci od początku bieżącej rutyny do punktu, w którym jest wywoływana ta metoda.
Napiwek
W odniesieniu do iniekcji zależności, podczas rejestrowania usług w IServiceCollection, okres istnienia usługi jest zarządzany automatycznie w Twoim imieniu. IServiceProvider i odpowiednie IHost koordynują czyszczenie 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.
Bezpieczne uchwyty
Pisanie kodu dla finalizatora obiektu jest złożonym zadaniem, które może powodować problemy, jeśli nie zostały wykonane poprawnie. Dlatego zalecamy konstruowanie obiektów System.Runtime.InteropServices.SafeHandle zamiast implementowania finalizatora.
System.Runtime.InteropServices.SafeHandle to abstrakcyjny typ zarządzany, który opakowuje System.IntPtr identyfikujący niezarządzany zasób. W systemie Windows może zidentyfikować uchwyt, a w systemie Unix deskryptor pliku.
SafeHandle
zapewnia całą logikę niezbędną do zapewnienia, że ten zasób jest zwalniany raz i tylko raz, gdy SafeHandle
zostanie usunięty lub gdy wszystkie odwołania do SafeHandle
zostały usunięte, a wystąpienie SafeHandle
zostanie zakończone.
System.Runtime.InteropServices.SafeHandle jest abstrakcyjną klasą bazową. Klasy pochodne zapewniają określone wystąpienia dla różnych rodzajów uchwytów. Te klasy pochodne weryfikują, jakie wartości dla System.IntPtr są uznawane za nieprawidłowe i jak faktycznie zwolnić uchwyt. Na przykład SafeFileHandle pochodzi z SafeHandle
, aby opakowować IntPtrs
, które identyfikują otwarte dojścia do plików/deskryptory, i przesłaniają metodę SafeHandle.ReleaseHandle(), aby ją zamknąć (za pośrednictwem funkcji close
w systemie Unix lub CloseHandle
w systemie Windows). Większość interfejsów API w bibliotekach platformy .NET, które tworzą niezarządzany zasób, opakowuje go w SafeHandle
i zwraca ten SafeHandle
do Ciebie, gdy jest to potrzebne, zamiast przekazywać nieprzetworzony wskaźnik. W sytuacjach, gdy wchodzisz w interakcję z komponentem niezarządzanym i otrzymujesz IntPtr
dla zasobu niezarządzanego, możesz utworzyć własny typ SafeHandle
, aby go opakować. W rezultacie niewiele typów innych niżSafeHandle
musi implementować finalizatory. Większość implementacji wzorców jednorazowego użytku kończy się jedynie na opakowywaniu innych zarządzanych zasobów, z których niektóre mogą być obiektami SafeHandle
.
Następujące klasy pochodne w przestrzeni nazw Microsoft.Win32.SafeHandles zapewniają bezpieczne dojścia.
Klasa | Przechowywane zasoby |
---|---|
SafeFileHandle SafeMemoryMappedFileHandle SafePipeHandle |
Pliki, mapowane w pamięci pliki i potoki |
SafeMemoryMappedViewHandle | Widoki pamięci |
SafeNCryptKeyHandle SafeNCryptProviderHandle SafeNCryptSecretHandle |
Konstrukcje kryptograficzne |
SafeRegistryHandle | Klucze rejestru |
SafeWaitHandle | Uchwyty synchronizacji |
Dispose() i Dispose(bool)
Interfejs IDisposable wymaga implementacji pojedynczej metody bez parametrów, Dispose. Ponadto każda klasa bez zapieczętowania powinna mieć metodę przeciążenia Dispose(bool)
.
Podpisy metod to:
-
public
metoda niewirtualna (NotOverridable
w Visual Basic) (implementacjaIDisposable.Dispose). -
protected virtual
(Overridable
w Visual Basic)Dispose(bool)
.
Metoda Dispose()
Ponieważ public
, metoda niewirtualna (NotOverridable
w Visual Basic) bez parametrów Dispose
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 być wykonywany. 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 odśmiecacz nie musi już wywoływać nadpisywanej przez obiekty metody Object.Finalize. W związku z tym wywołanie metody SuppressFinalize uniemożliwia mechanizmowi odśmiecania pamięci uruchomienie finalizatora. Jeśli typ nie ma finalizatora, wywołanie metody GC.SuppressFinalize nie ma wpływu. Rzeczywiste czyszczenie jest wykonywane przez przeciążenie metody Dispose(bool)
.
Przeciążenie metody Dispose(bool)
W przeciążeniu parametr disposing
jest Boolean, który wskazuje, czy wywołanie metody pochodzi z metody Dispose (wartość parametru to true
) albo z finalizatora (wartość parametru to 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
Ważny
Parametr disposing
powinien być false
, gdy jest wywoływany z finalizatora, a true
po wywołaniu z metody IDisposable.Dispose. Innymi słowy, jest to true
, gdy jest wywoływane w sposób deterministyczny i false
, gdy jest wywoływane w sposób niedeterministyczny.
Treść metody składa się z trzech bloków kodu:
Blok powrotu warunkowego, jeśli obiekt jest już usunięty.
Blok warunkowy, który zwalnia zarządzane zasoby. Ten blok jest wykonywany, jeśli wartość
disposing
jesttrue
. Zarządzane zasoby, które zwalnia, mogą obejmować:- Obiekty zarządzane implementujące IDisposable. Blok warunkowy można wykorzystać do wywoływania ich implementacji Dispose (kaskadowego usuwania zasobów). Jeśli użyto klasy pochodnej System.Runtime.InteropServices.SafeHandle do opakowania niezarządzanego zasobu, tutaj należy wywołać implementację SafeHandle.Dispose().
- 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 do
null
, aby zwiększyć prawdopodobieństwo, że staną się niedostępne. Spowoduje to ich szybsze wydanie niż w przypadku odzyskiwania ich w sposób niedeterministyczny.
Blok, który zwalnia niezarządzane zasoby. Ten blok jest wykonywany niezależnie od wartości parametru
disposing
.
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ść, w jakiej kolektor śmieci usuwa obiekty zarządzane podczas finalizowania, jest nieokreślona.
Wywołania usuwania kaskadowego
Jeśli klasa jest właścicielem pola lub właściwości, a jej typ implementuje IDisposable, sama klasa zawierająca powinna również implementować IDisposable. Klasa, która tworzy wystąpienie implementacji IDisposable i przechowuje je jako pole instancji, jest również odpowiedzialna za jego czyszczenie. Pomaga to zagwarantować, że przywoływane typy jednorazowe mają możliwość deterministycznego czyszczenia za pomocą metody Dispose. W poniższym przykładzie klasa jest 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ść IDisposable, ale nie własnej, co oznacza, że klasa nie tworzy obiektu, klasa nie musi implementować IDisposable.
- Istnieją przypadki, w których można wykonać
null
-sprawdzanie w finalizatorze (obejmującym metodęDispose(false)
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 niezapieczętowane klasy (lub klasy Visual Basic niezmodyfikowane jako NotInheritable
) powinny być traktowane jako potencjalne klasy bazowe, 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 faktyczne oczyszczanie. - Klasa pochodząca z SafeHandle, która opakowuje niezarządzany zasób (zalecane) lub zastąpi metodę Object.Finalize. Klasa SafeHandle udostępnia finalizator, więc nie musisz go pisać samodzielnie.
Ważny
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
Notatka
W poprzednim przykładzie użyto obiektu SafeFileHandle do zilustrowania wzorca; zamiast tego można użyć dowolnego obiektu pochodzącego z SafeHandle. Zwróć uwagę, że w przykładzie jego obiekt SafeFileHandle nie jest poprawnie zainicjowany.
Oto ogólny wzorzec implementowania wzorca usuwania dla klasy bazowej, która zastępuje Object.Finalize.
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
Wskazówka
W języku C#zaimplementujesz finalizację, udostępniając finalizator, a nie przez zastąpienie Object.Finalize. W języku Visual Basic utworzysz finalizator z Protected Overrides Sub Finalize()
.
Implementowanie wzorca usuwania dla klasy pochodnej
Klasa pochodna klasy, która implementuje interfejs IDisposable, nie powinna implementować IDisposable, ponieważ implementacja klasy bazowej IDisposable.Dispose jest dziedziczona przez klasy pochodne. Zamiast tego, aby wyczyścić klasę pochodną, należy podać następujące informacje:
- Metoda
protected override void Dispose(bool)
, 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 języku Visual Basic), przekazując jej stan dysponowania (parametrbool disposing
) jako argument. - Klasa pochodząca z SafeHandle, która opakowuje niezarządzany zasób (zalecane) 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ć przeciążoną wersję
Dispose(bool)
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
Notatka
W poprzednim przykładzie użyto obiektu SafeFileHandle do zilustrowania wzorca; zamiast tego można użyć dowolnego obiektu pochodzącego z SafeHandle. Zwróć uwagę, że w przykładzie nie jest poprawnie tworzona instancja 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