Sdílet prostřednictvím


Implementace metody Dispose

Metoda Dispose je primárně implementována pro uvolnění nespravovaných prostředků. Při práci s členy instance, které jsou implementace IDisposable, je běžné kaskádové volání Dispose. Existují další důvody pro implementaci Dispose, například uvolnění paměti, která byla přidělena, odebrání položky přidané do kolekce nebo signál uvolnění zámku, který byl získán.

Systém .NET garbage collector nepřiděluje ani neuvolňuje nespravovanou paměť. Vzor pro likvidaci objektu, označovaný jako vzor dispose, ukládá pořadí na dobu životnosti objektu. Vzor Dispose se používá pro objekty, které implementují rozhraní IDisposable. Tento vzor je běžný při interakci s popisovači souborů a rour, popisovači registru, popisovači čekání nebo ukazateli na bloky nespravované paměti, protože ten správce paměti nemůže uvolnit nespravované objekty.

Aby bylo možné zajistit, aby prostředky byly vždy správně vyčištěny, měla by být metoda Dispose idempotentní, aby byla volána vícekrát bez vyvolání výjimky. Další vyvolání Dispose by navíc nemělo dělat nic.

Příklad kódu zadaný pro metodu GC.KeepAlive ukazuje, jak proces uvolňování paměti může způsobit, že se finalizátor může spustit, i když nespravovaný odkaz na objekt nebo jeho členy je stále používán. Je vhodné využít GC.KeepAlive, aby byl objekt nezpůsobilý pro garbage collection od začátku aktuální rutiny do bodu, kde je tato metoda volána.

Spropitné

Pokud jde o injektování závislostí, při registraci služeb v IServiceCollectionse doba života služby implicitně spravuje vaším jménem. IServiceProvider a odpovídající IHost orchestrují čištění prostředků. Konkrétně jsou implementace IDisposable a IAsyncDisposable správně odstraněny na konci své stanovené životnosti.

Další informace naleznete v tématu Injektování závislostí v .NET.

Bezpečné úchyty

Psaní kódu pro finalizátor objektu je složitá úloha, která může způsobit problémy, pokud nejsou provedeny správně. Proto doporučujeme vytvořit System.Runtime.InteropServices.SafeHandle objekty místo implementace finalizátoru.

System.Runtime.InteropServices.SafeHandle je abstraktní spravovaný typ, který obaluje System.IntPtr identifikující nespravovaný prostředek. Ve Windows může jít o identifikaci popisovače a na Unixu o deskriptor souboru. SafeHandle poskytuje veškerou logiku potřebnou k tomu, aby byl tento prostředek uvolněn jednou a pouze jednou, a to buď při odstranění SafeHandle, nebo při vyřazení všech odkazů na SafeHandle a dokončení instance SafeHandle.

System.Runtime.InteropServices.SafeHandle je abstraktní základní třída. Odvozené třídy poskytují konkrétní instance pro různé typy popisovačů. Tyto odvozené třídy ověřují, jaké hodnoty pro System.IntPtr jsou považovány za neplatné a jak skutečně uvolnit popisovač. Například SafeFileHandle je odvozen od SafeHandle, aby zabalil IntPtrs, které identifikují otevřené popisovače souborů, a přepíše svou SafeHandle.ReleaseHandle() metodu k zavření (prostřednictvím funkce close v Unixu nebo funkce CloseHandle ve Windows). Většina rozhraní API v knihovnách .NET, které vytvářejí nespravovaný prostředek, ho zabalí do SafeHandle a podle potřeby vám vrátí SafeHandle, místo aby předala přímo nezpracovaný ukazatel. V situacích, kdy pracujete s nespravovanou komponentou a získáte IntPtr pro nespravovaný prostředek, můžete vytvořit vlastní typ SafeHandle k jeho obalení. V důsledku toho potřebuje jen málo typů, které nejsouSafeHandle, implementovat finalizátory. Většina implementací jednorázových vzorů končí pouze zabalením jiných spravovaných prostředků, z nichž některé můžou být SafeHandle objekty.

Následující odvozené třídy v oboru názvů Microsoft.Win32.SafeHandles poskytují bezpečné popisovače.

Třída Prostředky, které obsahuje
SafeFileHandle
SafeMemoryMappedFileHandle
SafePipeHandle
Soubory, soubory mapované v paměti a kanály
SafeMemoryMappedViewHandle Zobrazení paměti
SafeNCryptKeyHandle
SafeNCryptProviderHandle
SafeNCryptSecretHandle
Kryptografické konstrukce
SafeRegistryHandle Klíče registru
SafeWaitHandle Obslužné rutiny čekání

Dispose() a Dispose(bool)

Rozhraní IDisposable vyžaduje implementaci jedné metody bez parametrů Dispose. Všechny nezapečetěné třídy by také měly mít přetíženou metodu Dispose(bool).

Podpisy metod jsou:

  • public nevirtuální (NotOverridable v jazyce Visual Basic) (IDisposable.Dispose implementace).
  • protected virtual (Overridable v jazyce Visual Basic) Dispose(bool).

Metoda Dispose()

Vzhledem k tomu, public, non-virtual (NotOverridable v jazyce Visual Basic), je volána bezparametrová Dispose metoda, pokud už není potřeba (příjemcem typu), jeho účelem je uvolnit nespravované prostředky, provést obecné vyčištění a indikovat, že finalizátor, pokud je k dispozici, nemusí běžet. Uvolnění skutečné paměti přidružené ke spravovanému objektu je vždy úlohou garbage collectoru. Z tohoto důvodu má standardní implementaci:

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 provede veškeré vyčištění objektů, takže garbage collector už nemusí volat přepsání objektů pomocí Object.Finalize. Proto volání metody SuppressFinalize zabraňuje garbage collectoru ve spuštění finalizátoru. Pokud typ nemá žádný finalizátor, volání GC.SuppressFinalize nemá žádný vliv. Skutečné vyčištění provádí přetížení metody Dispose(bool).

Přetížení metody Dispose(bool)

V přetížení označuje parametr disposing typu Boolean, zda volání metody pochází z metody Dispose (její hodnota je true) nebo z ukončovací metody (její hodnota je 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

Důležitý

Parametr disposing by měl být při zavolání z finalizátoru false a při zavolání z metody IDisposable.Disposetrue. Jinými slovy, je to true, pokud je volán deterministicky, a false, pokud je volán nedeterministicky.

Tělo metody se skládá ze tří bloků kódu:

  • Blok pro podmíněné vrácení, pokud je objekt již zlikvidován.

  • Podmíněný blok, který uvolní spravované prostředky. Tento blok se spustí, pokud je hodnota disposingtrue. Mezi spravované prostředky, které uvolní, patří:

    • Spravované objekty, které implementují IDisposable. Podmíněný blok lze použít k volání implementace Dispose (kaskádová dispose). Pokud jste k zabalení nespravovaného prostředku použili odvozenou třídu System.Runtime.InteropServices.SafeHandle, měli byste zde zavolat metodu SafeHandle.Dispose().
    • Spravované objekty, které spotřebovávají velké množství paměti nebo využívají málo prostředků. Přiřaďte velké spravované objektové odkazy na null, aby byly s větší pravděpodobností nedostupnější. Tím se uvolní rychleji, než kdyby byly uvolněny nedeterministicky.
  • Blok, který uvolní nespravované prostředky. Tento blok se spustí bez ohledu na hodnotu parametru disposing.

Pokud volání metody pochází z finalizátoru, měl by se spustit pouze kód, který uvolní nespravované prostředky. Implementátor je odpovědný za to, aby falešná cesta neinteragovala se spravovanými objekty, které mohly být ukončeny. To je důležité, protože pořadí, ve kterém garbage collector zpracovává spravované objekty během finalizace, je nedeterministické.

Kaskádová volání dispose

Pokud vaše třída vlastní pole nebo vlastnost a jeho typ implementuje IDisposable, obsahující třída by měla také implementovat IDisposable. Třída, která vytvoří instanci implementace IDisposable a uloží ji jako člena instance, je také zodpovědná za její vyčištění. To pomáhá zajistit, aby odkazované uvolnitelné typy měly možnost předvídatelně provést vyčištění pomocí metody Dispose. V následujícím příkladu je třída sealed (nebo NotInheritable v jazyce 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

Spropitné

  • Pokud má vaše třída IDisposable pole nebo vlastnost, ale nemá vlastní, což znamená, že třída objekt nevytvoří, pak třída nemusí implementovat IDisposable.
  • Existují případy, kdy můžete chtít provést kontrolu null-ve finalizátoru (která zahrnuje metodu Dispose(false) vyvolanou finalizátorem). Jedním z hlavních důvodů je, že si nejste jistí, jestli se instance plně inicializovala (například výjimka může být vyvolána v konstruktoru).

Implementace vzoru Dispose

Všechny nezapečetěné třídy (nebo třídy jazyka Visual Basic, které nejsou upraveny jako NotInheritable), by se měly považovat za potenciální základní třídu, protože by mohly být zděděné. Pokud implementujete vzor Dispose pro libovolnou potenciální základní třídu, musíte zadat následující:

  • Implementace Dispose, která volá metodu Dispose(bool).
  • Metoda Dispose(bool), která provádí skutečné vyčištění.
  • Buď třída odvozená z SafeHandle, která obalí váš nespravovaný prostředek (doporučeno), nebo přepsání metody Object.Finalize. Třída SafeHandle poskytuje finalizátor, takže ho nemusíte sami psát.

Důležitý

Základní třída může odkazovat pouze na spravované objekty a implementovat vzor Dispose. V těchto případech je finalizátor zbytečný. Finalizátor se vyžaduje pouze v případě, že přímo odkazujete na nespravované prostředky.

Tady je obecný příklad implementace vzoru Dispose pro základní třídu, která používá bezpečný popisovač.

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

Poznámka

Předchozí příklad používá objekt SafeFileHandle k ilustraci vzoru; místo toho lze použít jakýkoli objekt odvozený z SafeHandle. Všimněte si, že se v příkladu nesprávně vytváří instance objektu SafeFileHandle.

Zde je obecný vzor pro implementaci modelu Dispose pro základní třídu, která přepisuje 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

Spropitné

V jazyce C# implementujete finalizaci tak, že poskytnete finalizační konstruktor , nikoli přepsáním Object.Finalize. V jazyce Visual Basic vytvoříte finalizér s Protected Overrides Sub Finalize().

Implementace vzoru Dispose Pattern pro odvozenou třídu

Třída odvozená z třídy, která implementuje IDisposable rozhraní by neměla implementovat IDisposable, protože základní třída implementace IDisposable.Dispose je zděděna jeho odvozenými třídami. Pokud chcete vyčistit odvozenou třídu, zadejte následující:

  • protected override void Dispose(bool) metoda, která přepíše metodu základní třídy a provádí skutečné vyčištění odvozené třídy. Tato metoda musí také volat metodu base.Dispose(bool) (MyBase.Dispose(bool) v jazyce Visual Basic) a předat jí stav vystavení (bool disposing parametr) jako argument.
  • Třída odvozená z SafeHandle, která zabalí nespravovaný prostředek (doporučeno), nebo přepsání metody Object.Finalize. Třída SafeHandle poskytuje finalizér, který vás zbaví nutnosti jej programovat. Pokud zadáte finalizátor, musí volat přetíženou funkci Dispose(bool) s argumentem false.

Tady je příklad běžného schématu pro implementaci vzoru Dispose pro odvozenou třídu, která používá bezpečnou rukojeť.

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

Poznámka

Předchozí příklad používá objekt SafeFileHandle k ilustraci vzoru; místo toho lze použít jakýkoli objekt odvozený z SafeHandle. Všimněte si, že v příkladu není objekt SafeFileHandle řádně instanciován.

Tady je obecný vzor pro implementaci vzoru Dispose pro odvozenou třídu, která přepisuje 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

Viz také