Implementacja metody Dispose

Implementacja Dispose metody jest przeznaczona głównie do wydawania zasobów niezarządzanych. Podczas pracy z członkami wystąpień, które są IDisposable implementacjami, często są wywoływane Dispose kaskadowo. Istnieją dodatkowe powody implementacji Disposeprogramu , na przykład w celu zwolnienia pamięci przydzielonej, usunięcia elementu dodanego do kolekcji lub sygnalizowania 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 w przypadku obiektów, które implementują IDisposable interfejs i 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. Dzieje się tak, ponieważ moduł odśmiecanie pamięci nie może odzyskać niezarządzanych obiektów.

Aby zapewnić, że zasoby są zawsze odpowiednio czyszczone, Dispose metoda powinna być idempotentna, tak aby można ją było wywołać wiele razy bez zgłaszania wyjątku. Ponadto kolejne wywołania nie Dispose powinny nic robić.

W przykładzie kodu podanym GC.KeepAlive dla metody pokazano, jak odzyskiwanie pamięci może spowodować uruchomienie finalizatora, podczas gdy niezarządzane odwołanie do obiektu lub jego elementów członkowskich jest nadal używane. Może to mieć sens, aby obiekt GC.KeepAlive był niekwalifikowany do odzyskiwania pamięci od początku bieżącej procedury do punktu, w którym jest wywoływana ta metoda.

Porada

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 odpowiednio IHost organizuj oczyszczanie 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 to abstrakcyjny typ zarządzany, który opakowuje System.IntPtr zasób niezarządzany. Na Windows może zidentyfikować dojście w systemie Unix, deskryptor plików. Zapewnia całą logikę niezbędną do zapewnienia, że ten zasób jest zwalniany raz i tylko raz, gdy zostanie usunięty lub gdy SafeHandle wszystkie odwołania do obiektu SafeHandle zostały usunięte, a SafeHandle wystąpienie zostanie sfinalizowane.

Jest System.Runtime.InteropServices.SafeHandle to abstrakcyjna klasa bazowa. Klasy pochodne zapewniają określone wystąpienia dla różnych rodzajów dojść. Te klasy pochodne sprawdzają, jakie wartości są System.IntPtr uznawane za nieprawidłowe i jak faktycznie zwolnić dojście. Na przykład SafeFileHandle element pochodzi z SafeHandle metody , aby opakowywaćIntPtrs, które identyfikują otwarte dojścia do plików/deskryptory, i przesłaniają jego SafeHandle.ReleaseHandle() metodę w celu jego zamknięcia (za pośrednictwem close funkcji w systemie Unix lub CloseHandle funkcji w Windows). Większość interfejsów API w bibliotekach .NET, które tworzą niezarządzany zasób, opakowuje go w SafeHandle obiekcie i zwraca je SafeHandle zgodnie z potrzebami, zamiast przekazywać pierwotny wskaźnik. W sytuacjach, w których wchodzisz w interakcję ze składnikiem niezarządzanym i uzyskujesz element IntPtr dla niezarządzanego zasobu, możesz utworzyć własny SafeHandle typ, aby go opakować. W związku z tym kilka typów innych niż typySafeHandle muszą implementować finalizatory. Większość implementacji wzorca jednorazowego kończy się tylko zawijaniem innych zasobów zarządzanych, z których niektóre mogą być SafeHandle.

Następujące klasy pochodne w Microsoft.Win32.SafeHandles przestrzeni nazw zapewniają bezpieczne dojścia:

Dispose() i Dispose(bool)

Interfejs IDisposable wymaga implementacji pojedynczej metody bez parametrów , Dispose. Ponadto każda klasa bez zapieczętowania powinna mieć dodatkową Dispose(bool) metodę przeciążenia.

Podpisy metod są następujące:

  • publicnon-virtual (NotOverridable w Visual Basic) (IDisposable.Dispose implementacja).
  • protected virtual(Overridable w Visual Basic) Dispose(bool).

Metoda Dispose()

Ponieważ metoda , niewirtualizowana public(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 istnieje, 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 modułowi odśmiecaniu pamięci uruchomienie finalizatora. Jeśli typ nie ma finalizatora, wywołanie GC.SuppressFinalize metody nie ma żadnego wpływu. Należy pamiętać, że 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 po wywołaniu IDisposable.Dispose z metody . Innymi słowy, jest true to, gdy deterministycznie wywoływane 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 (kaskadowa likwidacja). Jeśli użyto klasy pochodnej System.Runtime.InteropServices.SafeHandle do opakowania niezarządzanego zasobu, należy wywołać implementację SafeHandle.Dispose() w tym miejscu.

    • Zarządzane obiekty, 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 zwiększyć prawdopodobieństwo, że będą one niedostępne. To zwalnia je szybciej niż wtedy, gdy zostały odzyskane niedeterministycznie.

Jeśli wywołanie metody pochodzi z finalizatora, powinien zostać wykonany tylko kod, który zwalnia niezarządzane zasoby. Implementator jest odpowiedzialny za zapewnienie, że ścieżka false nie współdziała 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 niedeterministyczna.

Wywołania usuwania kaskadowego

Jeśli klasa jest właścicielem pola lub właściwości, a jej typ implementuje IDisposableelement , klasa zawierająca również powinna implementować element IDisposable. Klasa, która tworzy wystąpienie implementacji IDisposable i przechowuje ją jako element członkowski wystąpienia, jest również odpowiedzialna za jej oczyszczanie. Ma to na celu zapewnienie, że przywoływane typy jednorazowe mają możliwość deterministycznego Dispose czyszczenia za pomocą metody . W tym 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

Porada

Istnieją przypadki, gdy może być konieczne przeprowadzenie nullsprawdzania 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 Visual Basic niezmodyfikowane jako NotInheritable) powinny być uznawane za potencjalną klasę bazową, 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) wykonująca rzeczywiste czyszczenie.
  • Klasa pochodząca z SafeHandle tej klasy opakowuje niezarządzany zasób (zalecany) lub zastępuje 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 zarządzanych obiektów i implementować wzorzec usuwania. W takich przypadkach finalizator nie jest potrzebny. Finalizator jest wymagany tylko wtedy, gdy bezpośrednio odwołujesz się do niezarządzanych zasobów.

Oto przykład ogólnego wzorca implementowania wzorca usuwania dla klasy bazowej, która używa bezpiecznego uchwytu.

using Microsoft.Win32.SafeHandles;
using System;
using System.Runtime.InteropServices;

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

    // Protected implementation of Dispose pattern.
    protected virtual void Dispose(bool disposing)
    {
        if (!_disposedValue)
        {
            if (disposing)
            {
                _safeHandle.Dispose();
            }

            _disposedValue = true;
        }
    }
}
Imports Microsoft.Win32.SafeHandles
Imports System.Runtime.InteropServices

Class BaseClassWithSafeHandle : Implements IDisposable
    ' Flag: Has Dispose already been called?
    Dim disposed As Boolean = False
    ' Instantiate a SafeHandle instance.
    Dim handle 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(disposing As Boolean)
        If disposed Then Return

        If disposing Then
            handle.Dispose()
        End If

        disposed = True
    End Sub
End Class

Uwaga

W poprzednim przykładzie użyto SafeFileHandle obiektu w celu zilustrowania wzorca. Zamiast tego można użyć dowolnego obiektu pochodzącego z SafeHandle metody . Należy pamiętać, że w przykładzie nie jest poprawnie tworzone wystąpienie jego SafeFileHandle obiektu.

Oto ogólny wzorzec implementowania wzorca usuwania dla klasy bazowej, która zastępuje Object.Finalizeelement .

using System;

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;
        }
    }
}
Class BaseClassWithFinalizer : Implements IDisposable
    ' Flag: Has Dispose already been called?
    Dim disposed As Boolean = False

    ' 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(disposing As Boolean)
        If disposed Then Return

        If disposing Then
            ' Dispose managed objects that implement IDisposable.
            ' Assign null to managed objects that consume large amounts of memory or consume scarce resources.
        End If

        ' Free any unmanaged objects here.
        '
        disposed = True
    End Sub

    Protected Overrides Sub Finalize()
        Dispose(False)
    End Sub
End Class

Porada

W języku C# implementujesz finalizację, dostarczając finalizator, a nie przez zastąpienie elementu Object.Finalize. W Visual Basic utworzysz finalizator za pomocą polecenia Protected Overrides Sub Finalize().

Implementowanie wzorca usuwania dla klasy pochodnej

Klasa pochodna klasy, która implementuje IDisposable interfejs, nie powinna implementować IDisposableklasy , ponieważ implementacja klasy bazowej jest IDisposable.Dispose dziedziczona przez 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 oczyszczanie klasy pochodnej. Ta metoda musi również wywołać metodę base.Dispose(bool) (MyBase.Dispose(bool) w Visual Basic) przekazując ją jako argument.bool disposing
  • Klasa pochodząca z SafeHandle tej klasy opakowuje niezarządzany zasób (zalecane) lub przesłonięć metodę Object.Finalize . Klasa SafeHandle udostępnia finalizator, który zwalnia Cię od konieczności kodowania. Jeśli podasz finalizator, musi wywołać Dispose(bool) przeciążenie argumentem false .

Oto przykład ogólnego wzorca implementowania wzorca usuwania dla klasy pochodnej, która używa bezpiecznego uchwytu:

using Microsoft.Win32.SafeHandles;
using System;
using System.Runtime.InteropServices;

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

            _disposedValue = true;
        }

        // Call base class implementation.
        base.Dispose(disposing);
    }
}
Imports Microsoft.Win32.SafeHandles
Imports System.Runtime.InteropServices

Class DerivedClassWithSafeHandle : Inherits BaseClassWithSafeHandle
    ' Flag: Has Dispose already been called?
    Dim disposed As Boolean = False
    ' Instantiate a SafeHandle instance.
    Dim handle As SafeHandle = New SafeFileHandle(IntPtr.Zero, True)

    ' Protected implementation of Dispose pattern.
    Protected Overrides Sub Dispose(disposing As Boolean)
        If disposed Then Return

        If disposing Then
            handle.Dispose()
            ' Free any other managed objects here.
            '
        End If

        ' Free any unmanaged objects here.
        '
        disposed = True

        ' 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 pochodnego.SafeHandle Należy pamiętać, że przykład nie tworzy poprawnie wystąpienia obiektu SafeFileHandle .

Oto ogólny wzorzec implementowania wzorca usuwania dla klasy pochodnej, która zastępuje Object.Finalize:

class DerivedClassWithFinalizer : BaseClassWithFinalizer
{
    // To detect redundant calls
    private bool _disposedValue;

    ~DerivedClassWithFinalizer() => this.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);
    }
}
Class DerivedClassWithFinalizer : Inherits BaseClassWithFinalizer
    ' Flag: Has Dispose already been called?
    Dim disposed As Boolean = False

    ' Protected implementation of Dispose pattern.
    Protected Overrides Sub Dispose(disposing As Boolean)
        If disposed Then Return

        If disposing Then
            ' Dispose managed objects that implement IDisposable.
            ' Assign null to managed objects that consume large amounts of memory or consume scarce resources.
        End If

        ' Free any unmanaged objects here.
        '
        disposed = True

        ' Call the base class implementation.
        MyBase.Dispose(disposing)
    End Sub

    Protected Overrides Sub Finalize()
        Dispose(False)
    End Sub
End Class

Zobacz też