Sdílet prostřednictvím


Implementace metody Dispose

Metoda Dispose se primárně implementuje pro uvolnění nespravovaných prostředků. Při práci s členy instance, které jsou IDisposable implementacemi, je běžné kaskádové Dispose volání. Existují další důvody pro implementaci Dispose, například uvolnit paměť, která byla přidělena, odebrat položku přidanou do kolekce nebo signalizovat uvolnění zámku, který byl získán.

Uvolňování paměti .NET nepřiděluje ani nevyvolá nespravovanou paměť. Vzor pro likvidaci objektu, označovaný jako vzor dispose, ukládá pořadí na dobu životnosti objektu. Model Dispose se používá pro objekty, které implementují IDisposable rozhraní. Tento vzor je běžný při interakci s popisovači souborů a kanálu, popisovačů registru, popisovačů čekání nebo ukazatelů na bloky nespravované paměti, protože uvolňování paměti nemůže uvolnit nespravované objekty.

Aby bylo možné zajistit, aby se prostředky vždy správně vyčistily, Dispose měla by být metoda 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 uvedený pro metodu GC.KeepAlive ukazuje, jak může uvolňování paměti způsobit, že se finalizátor spustí, zatímco nespravovaný odkaz na objekt nebo jeho členy je stále používán. Použití objektu může být GC.KeepAlive vhodné k tomu, aby byl objekt způsobilý pro uvolňování paměti od začátku aktuální rutiny do bodu, kde je volána tato metoda.

Tip

Pokud jde o injektáž závislostí, při registraci služeb v rámci IServiceCollectionslužby je životnost služby spravována implicitně vaším jménem. Vyčištění IServiceProvider prostředků a odpovídající IHost orchestrace. Konkrétně implementace IDisposable a IAsyncDisposable jsou řádně uvolněny na konci jejich zadané životnosti.

Další informace naleznete v tématu Injektáž závislostí v .NET.

Sejf úchyty

Psaní kódu pro finalizační metodu objektu je složitý úkol, který může způsobit problémy, není-li prováděn správně. Proto doporučujeme, abyste místo implementace finalizátoru vytvořili System.Runtime.InteropServices.SafeHandle objekty.

A System.Runtime.InteropServices.SafeHandle je abstraktní spravovaný typ, který zabalí System.IntPtr nespravovaný prostředek. Ve Windows může popisovač identifikovat popisovač a v unixovém popisovači. Poskytuje SafeHandle veškerou logiku potřebnou k tomu, aby byl tento prostředek uvolněn jednou a pouze jednou, a to buď při SafeHandle vyřazení, nebo při vyřazení všech odkazů na SafeHandle tento prostředek a SafeHandle dokončení instance.

Jedná se System.Runtime.InteropServices.SafeHandle o abstraktní základní třídu. Odvozené třídy poskytují konkrétní instance pro různé druhy popisovačů. Tyto odvozené třídy ověřují, jaké hodnoty jsou System.IntPtr považovány za neplatné a jak skutečně uvolnit popisovač. Například odvozuje od zabaleníIntPtrs, SafeFileHandle které identifikují otevřené popisovače nebo popisovače souborů, a přepíše jeho SafeHandle.ReleaseHandle() metodu tak, aby ji zavřela (prostřednictvím close funkce v systému Unix nebo CloseHandle SafeHandle funkce ve Windows). Většina rozhraní API v knihovnách .NET, které vytvářejí nespravovaný prostředek, zabalí ho do SafeHandle a vrátí SafeHandle se k vám podle potřeby, a ne předání nezpracovaného ukazatele. V situacích, kdy pracujete s nespravovanou komponentou a získáte pro IntPtr nespravovaný prostředek, můžete vytvořit vlastní SafeHandle typ, který ji zabalí. V důsledku toho je potřeba implementovat finalizační metody několika typů, které nejsouSafeHandle typy. Většina jednorázových implementací 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 Microsoft.Win32.SafeHandles oboru názvů 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é bezparametrové metody , Dispose. Také všechny nezapečetěné třídy by měly mít metodu Dispose(bool) přetížení.

Podpisy metod jsou:

  • public non-virtual (NotOverridable v jazyce Visual Basic) (IDisposable.Dispose implementace).
  • protected virtual (Overridable v jazyce Visual Basic) Dispose(bool).

Metoda Dispose()

publicVzhledem k tomu, že , non-virtual (NotOverridable v jazyce Visual Basic), parametrless Dispose metoda je volána, když 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 doménou uvolňování paměti. 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 všechny vyčištění objektu, takže uvolňování paměti už nemusí volat přepsání Object.Finalize objektů. Proto volání SuppressFinalize metody zabraňuje uvolňování paměti 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í Dispose(bool) přetížení metody.

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

V přetížení je parametr, který označuje, disposing zda volání metody pochází z Dispose metody (jeho hodnota ) truenebo finalizátor (jeho hodnota je 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

Důležité

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

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

  • Blok pro podmíněný návrat, pokud je objekt již uvolněn.

  • Blok, který uvolní nespravované prostředky. Tento blok se spustí bez ohledu na hodnotu parametru disposing .

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

    • Spravované objekty, které implementují IDisposable. Podmíněný blok lze použít k volání jejich Dispose implementace (kaskádová dispose). Pokud jste k zabalení nespravovaného prostředku použili odvozenou třídu System.Runtime.InteropServices.SafeHandle , měli byste sem zavolat implementaci 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é odkazy na spravované objekty, aby null byly s větší pravděpodobností nedostupnější. Tím se uvolní rychleji, než kdyby byly uvolněny nedeterministicky.

Pokud volání metody pochází z finalizátoru, měl by se spustit pouze kód, který uvolní nespravované prostředky. Implementátor zodpovídá za to, že nepravdivá cesta nepracuje se spravovanými objekty, které mohly být uvolněny. To je důležité, protože pořadí, ve kterém uvolňování paměti vyhazuje 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 IDisposable implementace a uloží ji jako člen instance, je také zodpovědná za vyčištění. To pomáhá zajistit, aby odkazované uvolnitelné typy byly dány příležitostí deterministicky provést vyčištění prostřednictvím Dispose metody. V následujícím příkladu je sealed třída (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

Tip

  • Pokud má vaše třída IDisposable pole nebo vlastnost, ale nevlastní ho, což znamená, že třída nevytvoří objekt, pak třída nemusí implementovat IDisposable.
  • Existují případy, kdy můžete chtít provést nullkontrolu 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 měly být považovány 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 toho, že zabalí váš nespravovaný prostředek (doporučeno), nebo přepsání metody Object.Finalize . Třída SafeHandle poskytuje finalizační metodu, takže si ji nemusíte psát sami.

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á SafeFileHandle objekt k ilustraci vzoru. Místo toho lze použít jakýkoli objekt odvozený z SafeHandle tohoto objektu. Všimněte si, že v příkladu není správně vytvoření 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

Tip

V jazyce C# implementujete finalizaci tím, že poskytnete finalizační metodu, nikoli přepsáním Object.Finalize. V jazyce Visual Basic vytvoříte finalizační metodou Protected Overrides Sub Finalize().

Implementace vzoru Dispose pro odvozenou třídu

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

  • Metoda protected override void Dispose(bool) , která přepíše metodu základní třídy a provede 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), která jí předá stav vystavení (bool disposing parametr) jako argument.
  • Buď třída odvozená z SafeHandle toho, že zabalí váš nespravovaný prostředek (doporučeno), nebo přepsání metody Object.Finalize . Třída SafeHandle poskytuje finalizátor, který vás uvolní od nutnosti psaní kódu. Pokud zadáte finalizátor, musí volat Dispose(bool) přetížení s argumentem false .

Tady je příklad obecného vzoru pro implementaci vzoru Dispose pro odvozenou třídu, která používá bezpečný popisovač:

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á SafeFileHandle objekt k ilustraci vzoru. Místo toho lze použít jakýkoli objekt odvozený z SafeHandle tohoto objektu. Všimněte si, že v příkladu není správně vytvoření instance objektu SafeFileHandle .

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é