实现 Dispose 方法
Dispose 方法主要用于释放非托管资源。 处理 IDisposable 实现的实例成员时,通常会级联 Dispose 调用。 实现 Dispose还有其他原因,例如释放已分配的内存、删除添加到集合中的项,或者发出信号以释放已获得的锁。
.NET 垃圾回收器 不会分配或释放非托管内存。 一个用于处置对象的模式(称为 Dispose 模式)对对象的生命周期进行规范管理。 Dispose 模式用于实现 IDisposable 接口的对象。 与文件和管道句柄、注册表句柄、等待句柄或指向非托管内存块的指针交互时,此模式很常见,因为垃圾回收器无法回收非托管对象。
为了帮助确保资源始终被正确清理,Dispose 方法应是幂等的,这样可以多次调用该方法,而不会引发异常。 此外,Dispose 的后续调用不应执行任何操作。
为 GC.KeepAlive 方法提供的代码示例演示了垃圾回收如何引起终结器运行,而对该对象或其成员的非托管引用仍在使用中。 利用 GC.KeepAlive 使对象无法从当前例程的开头到调用此方法的点进行垃圾回收是有意义的。
提示
对于依赖关系注入,在 IServiceCollection 中注册服务时,会代表你隐式管理服务生存期。 IServiceProvider 和相应的 IHost 协调资源清理。 具体而言,IDisposable 和 IAsyncDisposable 的实现在其指定生存期结束时正确释放。
有关详细信息,请参阅 .NET 中的依赖关系注入。
安全句柄
为对象的终结器编写代码是一项复杂的任务,如果无法正确完成,可能会导致问题。 因此,建议你构造 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
对象。
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
方法在不再需要时(由该类型的使用者)调用,因此其用途是释放非托管资源,执行常规清理,以及指示终结器(如果存在)不必运行。 与托管对象关联的实际内存的释放始终由 垃圾回收器负责。 因此,它具有标准实现:
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
,而从 IDisposable.Dispose 方法调用时应为 true
。 换句话说,确定性调用时的情况是true
,不确定性调用时的情况是false
。
方法的正文由三个代码块组成:
如果对象已释放,则为条件返回块。
释放托管资源的条件块。 如果
disposing
的值是true
,则执行此块。 它释放的托管资源可包括:- 实现 IDisposable 的托管对象。 可用于调用其 Dispose 实现(级联释放)的条件块。 如果您已使用派生类 System.Runtime.InteropServices.SafeHandle 封装了非托管资源,则应在此调用 SafeHandle.Dispose() 的实现。
- 使用大量内存或消耗稀缺资源的托管对象。 将大型托管对象引用分配给
null
,使其更有可能无法访问。 相比以非确定性方式回收它们,这样做释放的速度更快。
释放非托管资源的块。 无论
disposing
参数的值如何,此块都会执行。
如果方法调用来自终结器,则应仅执行释放非托管资源的代码。 实现者负责确保伪路径不会与可能已释放的托管对象交互。 这一点很重要,因为垃圾回收器在终结过程中处理托管对象的顺序是不确定的。
级联释放调用
如果类拥有字段或属性及其类型实现 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。
- 在某些情况下,你可能希望在终结器(包括终结器调用的
Dispose(false)
方法)中执行null
检查。 主要原因之一是不确定实例是否已完全初始化(例如,可能会在构造函数中引发异常)。
实现释放模式
所有非密封类(或未修改为 NotInheritable
的 Visual Basic 类)都应被视为潜在的基类,因为它们可以继承。 如果为任何潜在的基类实现释放模式,则必须提供以下内容:
- 调用 Dispose 方法的
Dispose(bool)
实现。 - 执行实际清理的
Dispose(bool)
方法。 - 从包装非托管资源的 SafeHandle 派生的类(推荐),或对 Object.Finalize 方法的重写。 SafeHandle 类提供终结器,因此无需自己编写一个。
重要
基类可以仅引用托管对象并实现释放模式。 在这些情况下,不需要终结器。 仅当直接引用非托管资源时,才需要终结器。
这里有一个常规的例子,它实现了使用安全句柄的基类的释放模式。
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
参数)作为参数传递给它。- 从包装非托管资源的 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