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()

publicPonieważ 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 to true. 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 obejmuje Dispose(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 argumentem false .

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

Zobacz też