實作 Dispose 方法
Dispose 方法主要是為了釋放 Unmanaged 資源而實作。 使用 IDisposable 實作的執行個體成員時,通常會串聯 Dispose 呼叫。 實作 Dispose 具有其他原因,例如,若要釋放已配置的記憶體、移除已新增至集合的項目,或發出已取得鎖定的釋放訊號。
.NET 記憶體回收行程不會配置或釋放 Unmanaged 記憶體。 處置物件的模式稱為處置模式,會在物件的存留期上安排順序。 處置模式用於實作 IDisposable 介面的物件。 與檔案和管道控制代碼、登錄控制代碼、等待控制代碼或 Unmanaged 記憶體區塊的指標互動時,這種模式很常見,因為記憶體回收行程無法回收 Unmanaged 物件。
若要確保資源一律都能適當清除,應該實作 Dispose 方法,以便此方法應該能夠被呼叫多次而不會擲回例外狀況。 此外,後續 Dispose 的叫用應該不會執行任何動作。
為 GC.KeepAlive 方法提供的程式碼範例會示範記憶體回收如何在物件或其成員的 Unmanaged 參考仍在使用時執行完成項。 此時應該利用 GC.KeepAlive,使其從目前常式的開始至呼叫這個方法時都不適合記憶體回收。
提示
關於相依性插入,在 IServiceCollection 中註冊服務時,系統會代表您隱含管理服務存留期。 IServiceProvider 和對應的 IHost 會協調資源清除。 具體而言,IDisposable 和 IAsyncDisposable 的實作會在指定的存留期結束時正確處置。
如需詳細資訊,請參閱 .NET 的相依性插入。
安全控制代碼
撰寫物件完成項的程式碼是一項複雜的工作,若未正確撰寫,可能會造成問題。 因此,建議您建構 System.Runtime.InteropServices.SafeHandle 物件,而不要實作完成項。
System.Runtime.InteropServices.SafeHandle 是一種抽象 Managed 型別,用來包裝可識別 Unmanaged 資源的 System.IntPtr。 在 Windows 上,其可能會識別控制碼,以及 Unix 上的檔案描述項。 SafeHandle
會提供所有必要邏輯,以確保在處置 SafeHandle
時,或卸載所有 SafeHandle
的參考以及 SafeHandle
執行個體完成時,此資源都會釋放一次且僅限一次。
System.Runtime.InteropServices.SafeHandle 是抽象基底類別。 衍生類別會為不同類型的控制代碼提供特定的執行個體。 這些衍生類別會驗證 System.IntPtr 的哪些值被視為無效,以及如何實際釋放控制代碼。 例如,SafeFileHandle 衍生自 SafeHandle
以包裝 IntPtrs
來識別開啟的檔案控制代碼/描述項,並覆寫其 SafeHandle.ReleaseHandle() 方法,以透過 Unix 上的 close
函式或 Windows 上的 CloseHandle
函式來進行關閉。 建立 Unmanaged 資源的 .NET 程式庫中大部分的 API 都會包裝在 SafeHandle
中,並視需要將 SafeHandle
傳回給您,而非傳回原始指標。 在與 Unmanaged 元件互動並取得 Unmanaged 資源的 IntPtr
時,您可以建立自己的 SafeHandle
類型來進行包裝。 因此,少數非 SafeHandle
類型需要實作完成項。 大部分可處置的模式實作最後只會包裝其他 Managed 資源,其中一些可能是 SafeHandle
物件。
Microsoft.Win32.SafeHandles 命名空間中的下列衍生類別會提供安全控制代碼。
類別 | 其所保留的資源 |
---|---|
SafeFileHandle SafeMemoryMappedFileHandle SafePipeHandle |
檔案、記憶體對應檔案和管道 |
SafeMemoryMappedViewHandle | 記憶體檢視 |
SafeNCryptKeyHandle SafeNCryptProviderHandle SafeNCryptSecretHandle |
加密結構 |
SafeRegistryHandle | 登錄機碼 |
SafeWaitHandle | 等候控制代碼 |
Dispose() 和 Dispose(bool)
IDisposable 介面要求實作單一無參數方法 Dispose。 此外,任何非密封類別都應該有 Dispose(bool)
多載方法。
方法簽章是:
public
非虛擬 (Visual Basic 中的NotOverridable
) (IDisposable.Dispose 實作)。protected virtual
(在 Visual Basic 中為Overridable
)Dispose(bool)
。
Dispose() 方法
由於這個 public
、非虛擬 (在 Visual Basic 中為 NotOverridable
)、無參數的 Dispose
方法於不再需要時呼叫 (由類型消費者所呼叫),其用途是釋放 Unmanaged 資源、執行一般清除,以及指出完成項 (如果有的話) 不需要執行。 釋放與 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)
{
// 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
重要
從完成項呼叫時,disposing
參數應為 false
,而從 IDisposable.Dispose 方法呼叫時應為 true
。 換句話說,其在決定性呼叫時為 true
,而在非決定性呼叫時為 false
。
方法的主體是由兩個程式碼區塊所構成:
如果物件已處置,則為條件式傳回的區塊。
釋放 Unmanaged 資源的區塊。 不論
disposing
參數的值為何,這個區塊都會執行。釋放 Managed 資源的條件性區塊。 如果
disposing
的值為true
,這個區塊就會執行。 它所釋放的 Managed 資源可能包括:實作 IDisposable 的 Managed 物件。 條件式區塊可以用來呼叫其 Dispose 實作 (串聯 Dispose)。 如果您已使用 System.Runtime.InteropServices.SafeHandle 的衍生類別來包裝 Unmanaged 資源,則應該在這裡呼叫 SafeHandle.Dispose() 實作。
耗用大量記憶體或耗用少量資源的 Managed 物件。 將大型 Managed 物件參考指派給
null
,使其更容易無法連線。 這會比以非決定性的方式回收時更快地回收。
如果方法呼叫來自完成項,則僅應執行釋放 Unmanaged 資源的程式碼。 實作者負責確保 false 路徑不會與可能已處置的 Managed 物件互動。 這很重要,因為記憶體回收行程在最終處理期間處置 Managed 物件的順序不具決定性。
串聯 Dispose 呼叫
如果您的類別擁有欄位或屬性,且其類型實作 IDisposable,則包含類別本身也應該實作 IDisposable。 具現化 IDisposable 實作並將其儲存為執行個體成員的類別也負責其清除。 這有助於確保參考的可處置類型有機會透過 Dispose 方法以決定性方式執行清除。 在下列範例中,類別 sealed
(或 Visual Basic 中的 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。
- 在某些情況下,您可能會想要執行完成項中的
null
-checking (其中包含完成項所叫用的方法Dispose(false)
)。 其中一個主要原因是如果您不確定執行個體是否已完全初始化 (例如,建構函式中可能會擲回例外狀況)。
實作處置模式
所有非密封類別 (或 Visual Basic 類別未修改為 NotInheritable
),應該視為潛在的基底類別,因為可以繼承。 如果您實作任何潛在基底類別的處置模式,則必須提供下列項目:
- 呼叫 Dispose 方法的
Dispose(bool)
實作。 - 執行實際清除的
Dispose(bool)
方法。 - 衍生自包裝您的 Unmanaged 資源之 SafeHandle 的類別 (建議使用),或式 Object.Finalize 方法的覆寫。 SafeHandle 類別提供完成項,因此您不需要自行撰寫。
重要
基底類別只能參考 Managed 物件並實作處置模式。 在這些情況下,不需要完成項。 只有在您直接參考 Unmanaged 資源時,才需要完成項。
以下一般範例會實作使用安全控制代碼的基底類別處置模式。
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
注意
上一個範例使用 SafeFileHandle 物件來說明模式;可改用任何衍生自 SafeHandle 的物件。 請注意,該範例未正確地執行個體化其 SafeFileHandle 物件。
以下一般模式將會實作覆寫 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
提示
在 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
參數)。 - 衍生自包裝您的 Unmanaged 資源之 SafeHandle 的類別 (建議使用),或式 Object.Finalize 方法的覆寫。 SafeHandle 類別會提供完成項,讓您不必自行撰寫程式碼。 如果您提供了完成項,則其必須使用
false
引數呼叫Dispose(bool)
多載。
以下一般模式的範例將會實作使用安全控制代碼之衍生類別的處置模式。
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
注意
上一個範例使用 SafeFileHandle 物件來說明模式;可改用任何衍生自 SafeHandle 的物件。 請注意,該範例未正確地執行個體化其 SafeFileHandle 物件。
以下一般模式將會實作覆寫 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