共用方式為


實作 Dispose 方法

Dispose 方法主要是為了釋放 Unmanaged 資源而實作。 使用 IDisposable 實作的實例成員時,通常會連續呼叫 Dispose。 執行 Dispose還有其他原因,例如,要釋放已配置的記憶體、移除新增至集合的項目,或發出釋放已取得鎖定的信號。

.NET 垃圾收集器 不會配置或釋放非受控記憶體。 處置物件的模式,稱為 處置模式,會對物件的存留期施加順序。 處置模式適用於實作 IDisposable 介面的物件。 與檔案和管道控制碼、登錄控制碼、等待控制碼或未受控的記憶體區塊指標互動時,這種模式很常見,因為垃圾回收器無法回收未受控物件。

為了協助確保資源獲得適當清理,Dispose 方法應該具有等冪性,如此便能多次呼叫,而不會擲回例外狀況。 此外,後續 Dispose 調用應該不會執行任何動作。

GC.KeepAlive 方法提供的程式碼範例顯示垃圾收集器如何在物件或其成員的非受管參考仍在使用時執行終結器。 利用 GC.KeepAlive 從目前程式的開始到呼叫該方法的位置,使該對象不符合垃圾回收的條件。

小技巧

就相依性注入而言,當您在 IServiceCollection中註冊服務時,會代表您隱含管理 服務存留期IServiceProvider 和對應的 IHost 協調資源清理。 具體來說,IDisposableIAsyncDisposable 的實作會在指定的使用週期結束時正確處置。

如需詳細資訊,請參閱 .NET 中的相依性插入。

串聯處置呼叫

如果您的類別擁有另一個實作 IDisposable型別的實體,則包含類別本身也應該實作 IDisposable。 通常,一個實例化 IDisposable 實作並儲存為實例成員(或屬性)的類別,也需負責其清除工作。 這有助於確保引用的可處置類型有機會透過 Dispose 方法以確定的方式執行清除。 在下列範例中,類別是在 Visual Basic 中 sealed (或 NotInheritable)。

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

小技巧

  • 如果您的類別具有 IDisposable 字段或屬性,但並未 擁有這些字段或屬性,則類別不需要實作 IDisposable。 一般而言,建立和儲存 IDisposable 子對象的類別也會成為擁有者,但在某些情況下,擁有權可以轉移到另一個 IDisposable 類型。
  • 在某些情況下,您可能會想要在終結器中執行 null檢查(這包括被終結器調用的 Dispose(false) 方法)。 其中一個主要原因是,如果不確定實例是否已完全初始化(例如,在建構函式中可能會拋出例外)。

Dispose() 和 Dispose(bool)

IDisposable 介面需要實作單一無參數方法,Dispose。 此外,任何非密封類別都應該具有名為 Dispose(bool) 的多載方法。

方法簽章如下:

  • public 非虛擬(在 Visual Basic 中為NotOverridable)(IDisposable.Dispose 實作)。
  • protected virtual (Visual Basic 中的OverridableDispose(bool)

Dispose() 方法

因為當 public的非虛擬(Visual Basic 中的NotOverridable)且無參數的 Dispose 方法不再被類型的取用者需要時會被呼叫,其目的是釋放非受控資源、執行一般清除工作,並且指出如果存在完成項則不需要執行它。 釋放與 Managed 物件相關聯的實際記憶體,始終是 垃圾收集器的職責,。 因此,它具有標準實作:

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

Dispose 方法會執行所有物件清理,因此垃圾回收器不再需要呼叫物件的 Object.Finalize 覆寫方法。 因此,呼叫 SuppressFinalize 方法可防止垃圾收集器執行終結器。 如果類型沒有完成項,則對 GC.SuppressFinalize 的呼叫沒有作用。 實際的清除是由 Dispose(bool) 方法過載所執行。

Dispose(bool) 方法多載

在多載中,disposing 參數是 Boolean,指出方法呼叫是否來自 Dispose 方法(其值是 true),還是來自完成項(其值為 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

重要

當從完成項呼叫時,disposing 參數應該是 false,而當從 true 方法呼叫時,應該是 IDisposable.Dispose。 換句話說,當以決定性方式呼叫 時,true,並在非決定性呼叫時 false

方法的主體包含三個程式代碼區塊:

  • 如果物件已經被處置,則這是一個用於條件式返回的區塊。

  • 釋放受控資源的條件式區塊。 如果 disposing 的值是 true,這個區塊就會執行。 其釋放的受控資源可以包括:

    • 實作 IDisposable的管理物件。 條件式區塊可用來呼叫其 Dispose 實作(層疊釋放)。 如果您使用 System.Runtime.InteropServices.SafeHandle 衍生類別來包裝非受控資源,您應該在這裡呼叫 SafeHandle.Dispose() 的實作。
    • Managed物件是使用大量記憶體或耗用稀缺資源的物件。 將大型 Managed 物件參考指派給 null,使其更可能無法到達。 這會比不具決定性的回收更快釋放它們。
  • 釋放非受控資源的區塊。 不論 disposing 參數的值為何,此區塊都會執行。

如果方法調用來自終結器,則應僅執行釋放非受控資源的程式碼。 執行者負責確保虛假路徑不會與可能已處置的受管理物件互動。 這很重要,因為垃圾收集器在最終處理期間處置受控對象的順序不具決定性。

實作釋放模式

所有非密封類別(或未修改為 NotInheritable的 Visual Basic 類別)都應該視為潛在的基類,因為它們可以繼承。 如果您為任何可能的基礎類別實作 Dispose 模式,必須將下列方法新增至您的類別中:

  • 呼叫 Dispose(bool) 方法的 Dispose 實作。
  • 執行實際清除的 Dispose(bool) 方法。
  • 如果您的類別處理非受控資源,請提供對Object.Finalize方法的覆寫,或將非受控資源包裝在SafeHandle中。

重要

只有當您直接參考非受控資源時,才需要使用終結器(Object.Finalize 覆寫)。 這是一種高度進階的案例,通常可以避免:

  • 如果您的類別只參考 managed 物件,則類別仍可能實作處置模式。 不需要實作終結器。
  • 如果您需要處理非受控資源,強烈建議您將 Unmanaged IntPtr 句柄包裝成 SafeHandleSafeHandle 提供終結器,因此您不需要自己編寫。 如需詳細資訊,請參閱 安全句柄 段落。

具有受控資源的基類

以下是實作只擁有受控資源的基類處置模式的一般範例。

using System;
using System.IO;

public class DisposableBase : IDisposable
{
    // Detect redundant Dispose() calls.
    private bool _isDisposed;

    // Instantiate a disposable object owned by this class.
    private Stream? _managedResource = new MemoryStream();

    // 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 (!_isDisposed)
        {
            _isDisposed = true;

            if (disposing)
            {
                // Dispose managed state.
                _managedResource?.Dispose();
                _managedResource = null;
            }
        }
    }
}
Imports System.IO

Public Class DisposableBase
    Implements IDisposable

    ' Detect redundant Dispose() calls.
    Private _isDisposed As Boolean

    ' Instantiate a disposable object owned by this class.
    Private _managedResource As Stream = New MemoryStream()

    ' 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 Not _isDisposed Then
            _isDisposed = True

            If disposing Then
                ' Dispose managed state.
                _managedResource?.Dispose()
                _managedResource = Nothing
            End If
        End If
    End Sub
End Class

注意

上述範例會使用 虛擬MemoryStream 對象來說明模式。 可以使用任何 IDisposable 來替代。

具有非受控資源的基類

以下是實作基類處置模式的範例,其會覆寫 Object.Finalize,以清除其擁有的 Unmanaged 資源。 此範例也示範了如何以執行緒安全的方式實作 Dispose(bool)。 處理多線程應用程式中非受控資源時,同步處理可能很重要。 如先前所述,這是進階案例。

using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;

public class DisposableBaseWithFinalizer : IDisposable
{
    // Detect redundant Dispose() calls in a thread-safe manner.
    // _isDisposed == 0 means Dispose(bool) has not been called yet.
    // _isDisposed == 1 means Dispose(bool) has been already called.
    private int _isDisposed;

    // Instantiate a disposable object owned by this class.
    private Stream? _managedResource = new MemoryStream();

    // A pointer to 10 bytes allocated on the unmanaged heap.
    private IntPtr _unmanagedResource = Marshal.AllocHGlobal(10);

    ~DisposableBaseWithFinalizer() => 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)
    {
        // In case _isDisposed is 0, atomically set it to 1.
        // Enter the branch only if the original value is 0.
        if (Interlocked.CompareExchange(ref _isDisposed, 1, 0) == 0)
        {
            if (disposing)
            {
                _managedResource?.Dispose();
                _managedResource = null;
            }

            Marshal.FreeHGlobal(_unmanagedResource);
        }
    }
}
Imports System
Imports System.IO
Imports System.Runtime.InteropServices
Imports System.Threading

Public Class DisposableBaseWithFinalizer
    Implements IDisposable

    ' Detect redundant Dispose() calls in a thread-safe manner.
    ' _isDisposed == 0 means Dispose(bool) has not been called yet.
    ' _isDisposed == 1 means Dispose(bool) has been already called.
    Private _isDisposed As Integer

    ' Instantiate a disposable object owned by this class.
    Private _managedResource As Stream = New MemoryStream()

    ' A pointer to 10 bytes allocated on the unmanaged heap.
    Private _unmanagedResource As IntPtr = Marshal.AllocHGlobal(10)

    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(disposing As Boolean)
        ' In case _isDisposed is 0, atomically set it to 1.
        ' Enter the branch only if the original value is 0.
        If Interlocked.CompareExchange(_isDisposed, 1, 0) = 0 Then
            If disposing Then
                _managedResource?.Dispose()
                _managedResource = Nothing
            End If

            Marshal.FreeHGlobal(_unmanagedResource)
        End If
    End Sub
End Class

注意

  • 上一個範例會使用 AllocHGlobal,在建構函式中的 Unmanaged 堆積上配置 10 個字節,並藉由呼叫 FreeHGlobal釋放 Dispose(bool) 中的緩衝區。 這是用於示範用途的範例分配。
  • 同樣地,建議您避免實現終結器。 請參閱 使用自定義安全句柄 實作處置模式,以取得將非決定性最終化和同步處理委派給 SafeHandle的對等範例。

小技巧

在 C# 中,您可以藉由提供 完成項來實作最終化,而不是藉由覆寫 Object.Finalize。 在 Visual Basic 中,您會使用 Protected Overrides Sub Finalize()建立終結器。

實現衍生類別的處置模式

衍生自實作 IDisposable 介面的類別不應該實作 IDisposable,因為 IDisposable.Dispose 的基類實作是由其衍生類別繼承。 相反地,若要清理衍生類別,您可以提供以下內容:

  • 用來覆寫基類方法並實際執行衍生類別清理工作的 protected override void Dispose(bool) 方法。 這個方法也必須呼叫 base.Dispose(bool) (在 Visual Basic 中MyBase.Dispose(bool)) 方法,將處置狀態 (bool disposing 參數) 傳遞為自變數。
  • 一個衍生自 SafeHandle 的類別,用於包裝您的非受控資源(建議),或者覆寫 Object.Finalize 方法。 SafeHandle 類別提供一個完成項,讓您不必撰寫程序代碼。 如果您確實提供了一個終結器,則必須使用 Dispose(bool) 參數來呼叫 false 方法多載。

以下是實作使用安全控制代碼的衍生類別之 dispose 模式的一般範例:

using System.IO;

public class DisposableDerived : DisposableBase
{
    // To detect redundant calls
    private bool _isDisposed;

    // Instantiate a disposable object owned by this class.
    private Stream? _managedResource = new MemoryStream();

    // Protected implementation of Dispose pattern.
    protected override void Dispose(bool disposing)
    {
        if (!_isDisposed)
        {
            _isDisposed = true;

            if (disposing)
            {
                _managedResource?.Dispose();
                _managedResource = null;
            }
        }

        // Call base class implementation.
        base.Dispose(disposing);
    }
}
Imports System.IO

Public Class DisposableDerived
    Inherits DisposableBase

    ' To detect redundant calls
    Private _isDisposed As Boolean

    ' Instantiate a disposable object owned by this class.
    Private _managedResource As Stream = New MemoryStream()

    ' Protected implementation of Dispose pattern.
    Protected Overrides Sub Dispose(disposing As Boolean)
        If Not _isDisposed Then
            _isDisposed = True

            If disposing Then
                _managedResource?.Dispose()
                _managedResource = Nothing
            End If
        End If

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

注意

上一個範例會使用 SafeFileHandle 對象來說明模式;任何衍生自 SafeHandle 的物件都可以改用。 請注意,此範例無法正確具現化其 SafeFileHandle 物件。

以下是針對覆寫 Object.Finalize之衍生類別實作處置模式的一般模式:

using System.Threading;

public class DisposableDerivedWithFinalizer : DisposableBaseWithFinalizer
{
    // Detect redundant Dispose() calls in a thread-safe manner.
    // _isDisposed == 0 means Dispose(bool) has not been called yet.
    // _isDisposed == 1 means Dispose(bool) has been already called.
    private int _isDisposed;

    ~DisposableDerivedWithFinalizer() => Dispose(false);

    // Protected implementation of Dispose pattern.
    protected override void Dispose(bool disposing)
    {
        // In case _isDisposed is 0, atomically set it to 1.
        // Enter the branch only if the original value is 0.
        if (Interlocked.CompareExchange(ref _isDisposed, 1, 0) == 0)
        {
            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.
        }

        // Call the base class implementation.
        base.Dispose(disposing);
    }
}
Imports System.Threading

Public Class DisposableDerivedWithFinalizer
    Inherits DisposableBaseWithFinalizer

    ' Detect redundant Dispose() calls in a thread-safe manner.
    ' _isDisposed == 0 means Dispose(bool) has not been called yet.
    ' _isDisposed == 1 means Dispose(bool) has been already called.
    Private _isDisposed As Integer

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

    ' Protected implementation of Dispose pattern.
    Protected Overrides Sub Dispose(disposing As Boolean)
        ' In case _isDisposed is 0, atomically set it to 1.
        ' Enter the branch only if the original value is 0.
        If Interlocked.CompareExchange(_isDisposed, 1, 0) = 0 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.
        End If

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

安全把手

撰寫物件完成項的程式代碼是一項複雜的工作,如果未正確完成,可能會導致問題。 因此,我們建議您建構 System.Runtime.InteropServices.SafeHandle 物件,而不是實作終結器。

System.Runtime.InteropServices.SafeHandle 是一種抽象的管理型別,封裝識別非受控資源的 System.IntPtr。 在 Windows 上,它可能會識別句柄,並在 Unix 上識別檔案描述元。 SafeHandle 提供必要的所有邏輯,以確保此資源僅在一次且只有一次被釋放,無論是在 SafeHandle 被釋放時,還是當對 SafeHandle 的所有引用被移除且 SafeHandle 實例完成終結時。

System.Runtime.InteropServices.SafeHandle 是抽象基類。 衍生類別會為不同類型的控制柄提供特定實例。 這些衍生類別會驗證哪些 System.IntPtr 的值被視為無效,以及如何實際釋放句柄。 例如,SafeFileHandle 是從 SafeHandle 衍生出來的,用於包裝用來識別開啟的檔案句柄和描述符的 IntPtrs,並覆寫其 SafeHandle.ReleaseHandle() 方法來關閉它(在 Unix 上藉由 close 函式或在 Windows 上藉由 CloseHandle 函式)。 .NET 程式庫中建立 Unmanaged 資源的大部分 API 都會將其包裝在 SafeHandle 中,並視需要傳回該 SafeHandle,而不是傳回原始指標。 當您與非受控元件互動並取得非受控資源的 IntPtr 時,您可以建立自己的 SafeHandle 類型來將其封裝。 因此,很少有非SafeHandle 類型需要實作完成項。 大部分的可丟棄模式實作最終只會包裝其他 Managed 資源,其中有些可能是 SafeHandle 物件。

使用自訂安全控制代碼實作釋放模式

下列程式代碼示範如何藉由實作 SafeHandle來處理非受控資源。

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

// Wraps the IntPtr allocated by Marshal.AllocHGlobal() into a SafeHandle.
class LocalAllocHandle : SafeHandleZeroOrMinusOneIsInvalid
{
    private LocalAllocHandle() : base(ownsHandle: true) { }

    // No need to implement a finalizer - SafeHandle's finalizer will call ReleaseHandle for you.
    protected override bool ReleaseHandle()
    {
        Marshal.FreeHGlobal(handle);
        return true;
    }

    // Allocate bytes with Marshal.AllocHGlobal() and wrap the result into a SafeHandle.
    public static LocalAllocHandle Allocate(int numberOfBytes)
    {
        IntPtr nativeHandle = Marshal.AllocHGlobal(numberOfBytes);
        LocalAllocHandle safeHandle = new LocalAllocHandle();
        safeHandle.SetHandle(nativeHandle);
        return safeHandle;
    }
}

public class DisposableBaseWithSafeHandle : IDisposable
{
    // Detect redundant Dispose() calls.
    private bool _isDisposed;

    // Managed disposable objects owned by this class
    private LocalAllocHandle? _safeHandle = LocalAllocHandle.Allocate(10);
    private Stream? _otherUnmanagedResource = new MemoryStream();

    // 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 (!_isDisposed)
        {
            _isDisposed = true;

            if (disposing)
            {
                // Dispose managed state.
                _otherUnmanagedResource?.Dispose();
                _safeHandle?.Dispose();
                _otherUnmanagedResource = null;
                _safeHandle = null;
            }
        }
    }
}
Imports System
Imports System.IO
Imports System.Runtime.InteropServices
Imports Microsoft.Win32.SafeHandles

' Wraps the IntPtr allocated by Marshal.AllocHGlobal() into a SafeHandle.
Public Class LocalAllocHandle
    Inherits SafeHandleZeroOrMinusOneIsInvalid

    Private Sub New()
        MyBase.New(True)
    End Sub

    ' No need to implement a finalizer - SafeHandle's finalizer will call ReleaseHandle for you.
    Protected Overrides Function ReleaseHandle() As Boolean
        Marshal.FreeHGlobal(handle)
        Return True
    End Function

    ' Allocate bytes with Marshal.AllocHGlobal() and wrap the result into a SafeHandle.
    Public Shared Function Allocate(numberOfBytes As Integer) As LocalAllocHandle
        Dim nativeHandle As IntPtr = Marshal.AllocHGlobal(numberOfBytes)
        Dim safeHandle As New LocalAllocHandle()
        safeHandle.SetHandle(nativeHandle)
        Return safeHandle
    End Function
End Class

Public Class DisposableBaseWithSafeHandle
    Implements IDisposable

    ' Detect redundant Dispose() calls.
    Private _isDisposed As Boolean

    ' Managed disposable objects owned by this class
    Private _safeHandle As LocalAllocHandle = LocalAllocHandle.Allocate(10)
    Private _otherUnmanagedResource As Stream = New MemoryStream()

    ' 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 Not _isDisposed Then
            _isDisposed = True

            If disposing Then
                ' Dispose managed state.
                _otherUnmanagedResource?.Dispose()
                _safeHandle?.Dispose()
                _otherUnmanagedResource = Nothing
                _safeHandle = Nothing
            End If
        End If
    End Sub
End Class

注意

DisposableBaseWithSafeHandle 類別的行為相當於上一個範例中 DisposableBaseWithFinalizer 類別的行為,不過這裡示範的方法更安全:

  • 不需要實作完成項,因為 SafeHandle 會負責完成。
  • 不需要同步處理以確保線程安全性。 雖然在 DisposableBaseWithSafeHandleDispose 實作中存在競爭條件,SafeHandle 確保 SafeHandle.ReleaseHandle 僅被呼叫一次。

.NET 中的內建安全句柄

Microsoft.Win32.SafeHandles 命名空間中的下列衍生類別提供安全句柄。

班級 其保留的資源
SafeFileHandle
SafeMemoryMappedFileHandle
SafePipeHandle
檔案、記憶體對應檔案和管線
SafeMemoryMappedViewHandle 內存檢視
SafeNCryptKeyHandle
SafeNCryptProviderHandle
SafeNCryptSecretHandle
密碼編譯建構
SafeRegistryHandle 登錄機碼
SafeWaitHandle 等候句柄

另請參閱