Dispose 方法主要用于释放非托管资源。 处理 IDisposable 实现的实例成员时,通常会级联 Dispose 调用。 实现 Dispose还有其他原因,例如释放已分配的内存、删除添加到集合中的项,或者发出信号以释放已获得的锁。
.NET 垃圾回收器 不会分配或释放非托管内存。 释放对象的模式(称为 释放模式)对对象的生存期施加顺序。 Dispose 模式用于实现 IDisposable 接口的对象。 与文件和管道句柄、注册表句柄、等待句柄或指向非托管内存块的指针交互时,此模式很常见,因为垃圾回收器无法回收非托管对象。
为了帮助确保资源始终被正确清理,Dispose 方法应是幂等的,这样可以多次调用该方法,而不会引发异常。 此外,Dispose 的后续调用不应执行任何操作。
为 GC.KeepAlive 方法提供的代码示例演示了垃圾回收如何引起终结器运行,而对该对象或其成员的非托管引用仍在使用中。 利用 GC.KeepAlive 使对象无法从当前例程的开头到调用此方法的点进行垃圾回收是有意义的。
提示
对于依赖关系注入,在 IServiceCollection 中注册服务时,会代表你隐式管理服务生存期。 IServiceProvider 和相应的 IHost 协调资源清理。 具体而言,IDisposable 和 IAsyncDisposable 的实现在其指定生存期结束时正确释放。
有关详细信息,请参阅 .NET 中的依赖关系注入。
级联释放调用
如果类拥有另一个实现 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。 通常,创建和存储 IDisposable 子对象的类也会成为所有者,但在某些情况下,所有权可以转移到另一 IDisposable 种类型。
- 在某些情况下,可能需要在终结器中进行
null检查(包括由终结器调用的Dispose(false)方法)。 主要原因之一是不确定实例是否已完全初始化(例如,可能会在构造函数中引发异常)。
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 方法在不再需要时(由该类型的使用者)调用,因此其用途是释放非托管资源,执行常规清理,以及指示终结器(如果存在)不必运行。 与托管对象关联的实际内存的释放始终由 垃圾回收器负责。 因此,它具有标准实现:
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() 的实现。
- 使用大量内存或消耗稀缺资源的托管对象。 将大型托管对象引用分配给
null,使其更有可能无法访问。 相比以非确定性方式回收它们,这样做释放的速度更快。
释放非托管资源的块。 无论
disposing参数的值如何,此块都会执行。
如果方法调用来自终结器,则应仅执行释放非托管资源的代码。 实现者负责确保伪路径不会与可能已释放的托管对象交互。 这一点很重要,因为垃圾回收器在终结过程中处理托管对象的顺序是不确定的。
实现释放模式
所有非密封类(或未修改为 NotInheritable的 Visual Basic 类)都应被视为潜在的基类,因为它们可以继承。 如果为任何潜在的基类实现释放模式,则必须将以下方法添加到类:
- 调用 Dispose 方法的
Dispose(bool)实现。 - 执行实际清理的
Dispose(bool)方法。 - 如果类涉及非托管资源,请重写Object.Finalize方法或将非托管资源包装在SafeHandle中。
重要
仅当直接引用非托管资源时,才需要终结器(重写 Object.Finalize)。 这是一种高度高级的方案,通常可以避免:
- 如果类仅引用托管对象,则类仍可能实现释放模式。 无需实现终结器。
- 如果需要处理非托管资源,强烈建议将非托管 IntPtr 句柄包装成 SafeHandle。 SafeHandle 提供了一个终结器,因此你不必自己编写。 有关详细信息,请参阅 “安全句柄 ”段落。
具有托管资源的基类
下面是为仅拥有托管资源的基类实现释放模式的一般示例。
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 以清理其拥有的非托管资源。 此示例还演示了一种以线程安全方式实现 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在构造函数中的非托管堆上分配 10 字节,并通过调用
Dispose(bool)释放缓冲区FreeHGlobal。 这是一种用于说明用途的示例分配。 - 同样,建议避免实现析构函数。 请参阅 使用自定义安全处理程序实现释放模式,查看与上一个示例等效的内容,该处理程序将非确定性终结和同步委托给 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重载。
以下是一个常规模式的示例,该模式用于实现使用安全句柄的派生类的释放模式:
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 库中的大多数 API 都会将非托管资源包装在一个 SafeHandle 中,并根据需要返回该 SafeHandle 资源,而不是直接返回原始指针。 在与非托管组件进行交互并获取非托管资源的 IntPtr 的情况下,你可以创建自己的 SafeHandle 类型进行包装。 因此,极少数非 SafeHandle 类型需要实现终结器。 大多数可丢弃模式实现最终会封装其他托管资源,其中一些可能是 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 将负责最终确定。
- 无需同步才能保证线程安全。 尽管在
Dispose的实现中存在竞态条件,SafeHandle仍然保证SafeHandle.ReleaseHandle仅被调用一次。
.NET 中的内置安全句柄
Microsoft.Win32.SafeHandles 命名空间中的以下派生类提供安全句柄。
| 类 | 它保存的资源 |
|---|---|
| SafeFileHandle SafeMemoryMappedFileHandle SafePipeHandle |
文件、内存映射文件和管道 |
| SafeMemoryMappedViewHandle | 内存视图 |
| SafeNCryptKeyHandle SafeNCryptProviderHandle SafeNCryptSecretHandle |
加密构造 |
| SafeRegistryHandle | 注册表项 |
| SafeWaitHandle | 等待句柄 |