实现 Dispose 方法

实现 Dispose 方法主要用于释放非托管资源。 处理 IDisposable 实现的实例成员时,通常会级联 Dispose 调用。 实现 Dispose 有其他原因,例如,为了释放已分配的内存、删除已添加到集合中的项,或发出释放已获取的锁的信号。

.NET 垃圾回收器不会分配或释放非托管内存。 对象释放模式(称为“释放模式”)会对对象生存期强制施加顺序。 释放模式用于实现 IDisposable 接口的对象,在与文件和管道句柄、注册表句柄、等待句柄或指向非托管内存块的指针交互时较为常见。 这是因为垃圾回收器无法回收非托管对象。

若要帮助确保始终适当地清理资源,Dispose 方法应为幂等,这样可以多次调用而不引发异常。 此外,Dispose 的后续调用不应执行任何操作。

GC.KeepAlive 方法提供的代码示例演示了垃圾回收如何引起终结器运行,而对该对象或其成员的非托管引用仍在使用中。 利用 GC.KeepAlive使对象从当前例程开始到调用此方法的那一刻为止都不适合进行垃圾回收,这是可行的。

提示

对于依赖关系注入,在 IServiceCollection 中注册服务时,会代表你隐式管理IServiceCollectionIServiceProvider 和相应的 IHost 协调资源清理。 具体而言,IDisposableIAsyncDisposable 的实现在其指定生存期结束时正确释放。

有关详细信息,请参阅 .NET 中的依赖关系注入

安全句柄

编写对象终结器的代码是一项复杂的任务,如果处理不好可能会出现问题。 因此,建议你构造 System.Runtime.InteropServices.SafeHandle 对象,而非实现终结器。

System.Runtime.InteropServices.SafeHandle 是一种抽象托管类型,该类型包装了可标识非托管资源的 System.IntPtr。 在 Windows 上,它可能标识一个句柄,而在 Unix 上则可能标识一个文件描述符。 它提供了所有必要的逻辑,以确保在处理 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 命名空间中的以下派生类提供安全句柄:

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 方法在不再需要时(由该类型的使用者)调用,因此其用途是释放非托管资源,执行常规清理,以及指示终结器(如果存在)不必运行。 释放与托管对象关联的实际内存始终是垃圾回收器的域。 因此,它具有标准实现:

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

方法的主体包含三个代码块:

  • 如果对象已释放,则为条件返回块。

  • 释放非托管资源的块。 无论 disposing 参数的值如何,都会执行此块。

  • 释放托管资源的条件块。 如果 disposing 的值为 true,则执行此块。 它释放的托管资源可包括:

    • 实现 IDisposable 的托管对象。 可用于调用其 Dispose 实现(级联释放)的条件块。 如果你已使用 System.Runtime.InteropServices.SafeHandle 的派生类来包装非托管资源,则应在此处调用 SafeHandle.Dispose() 实现。

    • 占用大量内存或使用短缺资源的托管对象。 将大型托管对象引用分配到 null,使它们更有可能无法访问。 相比以非确定性方式回收它们,这样做释放的速度更快。

如果方法调用来自终结器,则应仅执行释放非托管资源的代码。 实施者负责确保假路径不会与可能已被释放的托管对象交互。 这一点很重要,因为垃圾回收器在终止期间释放托管对象的顺序是不确定的。

级联释放调用

如果你的类拥有一个字段或属性,并且其类型实现 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

提示

在某些情况下,你可能希望在终结器(包括终结器调用的 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;

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);

    // Protected implementation of Dispose pattern.
    protected virtual void Dispose(bool disposing)
    {
        if (!_disposedValue)
        {
            if (disposing)
            {
                _safeHandle.Dispose();
            }

            _disposedValue = true;
        }
    }
}
Imports Microsoft.Win32.SafeHandles
Imports System.Runtime.InteropServices

Class BaseClassWithSafeHandle : Implements IDisposable
    ' Flag: Has Dispose already been called?
    Dim disposed As Boolean = False
    ' Instantiate a SafeHandle instance.
    Dim handle 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(disposing As Boolean)
        If disposed Then Return

        If disposing Then
            handle.Dispose()
        End If

        disposed = True
    End Sub
End Class

注意

上一个示例使用 SafeFileHandle 对象阐释模式;可以使用派生自 SafeHandle 的任何对象来替代。 请注意,该示例不会正确实例化其 SafeFileHandle 对象。

以下是一个常规模式,用于实现重写 Object.Finalize 的基类的释放模式。

using System;

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;
        }
    }
}
Class BaseClassWithFinalizer : Implements IDisposable
    ' Flag: Has Dispose already been called?
    Dim disposed As Boolean = False

    ' 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 disposed Then Return

        If disposing Then
            ' Dispose managed objects that implement IDisposable.
            ' Assign null to managed objects that consume large amounts of memory or consume scarce resources.
        End If

        ' Free any unmanaged objects here.
        '
        disposed = True
    End Sub

    Protected Overrides Sub Finalize()
        Dispose(False)
    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;

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();
            }

            _disposedValue = true;
        }

        // Call base class implementation.
        base.Dispose(disposing);
    }
}
Imports Microsoft.Win32.SafeHandles
Imports System.Runtime.InteropServices

Class DerivedClassWithSafeHandle : Inherits BaseClassWithSafeHandle
    ' Flag: Has Dispose already been called?
    Dim disposed As Boolean = False
    ' Instantiate a SafeHandle instance.
    Dim handle As SafeHandle = New SafeFileHandle(IntPtr.Zero, True)

    ' Protected implementation of Dispose pattern.
    Protected Overrides Sub Dispose(disposing As Boolean)
        If disposed Then Return

        If disposing Then
            handle.Dispose()
            ' Free any other managed objects here.
            '
        End If

        ' Free any unmanaged objects here.
        '
        disposed = True

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

注意

上一个示例使用 SafeFileHandle 对象阐释模式;可以使用派生自 SafeHandle 的任何对象来替代。 请注意,该示例不会正确实例化其 SafeFileHandle 对象。

以下是一个常规模式,用于实现重写 Object.Finalize 的派生类的释放模式:

class DerivedClassWithFinalizer : BaseClassWithFinalizer
{
    // To detect redundant calls
    private bool _disposedValue;

    ~DerivedClassWithFinalizer() => this.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);
    }
}
Class DerivedClassWithFinalizer : Inherits BaseClassWithFinalizer
    ' Flag: Has Dispose already been called?
    Dim disposed As Boolean = False

    ' Protected implementation of Dispose pattern.
    Protected Overrides Sub Dispose(disposing As Boolean)
        If disposed Then Return

        If disposing Then
            ' Dispose managed objects that implement IDisposable.
            ' Assign null to managed objects that consume large amounts of memory or consume scarce resources.
        End If

        ' Free any unmanaged objects here.
        '
        disposed = True

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

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

另请参阅