2

最近,我需要将 IDisposable 和对象终结的建议模式与 VS2005/VB.NET 提供的自动生成模式进行比较。我们已经使用了自动生成的一个,但是在并排查看它之后,我对 VB.NET 实现有很多疑问......

作为参考,这里是 IDE 的实现:

Public Class Class1
    Implements IDisposable

    Private disposedValue As Boolean = False        ''// To detect redundant calls

    ''// IDisposable
    Protected Overridable Sub Dispose(ByVal disposing As Boolean)
        If Not Me.disposedValue Then
            If disposing Then
                ''// TODO: free managed resources when explicitly called
            End If

            ''// TODO: free shared unmanaged resources
        End If
        Me.disposedValue = True
    End Sub

#Region " IDisposable Support "
    ''// This code added by Visual Basic to correctly implement the disposable pattern.
    Public Sub Dispose() Implements IDisposable.Dispose
        ''// Do not change this code.  Put cleanup code in Dispose(ByVal disposing As Boolean) above.
        Dispose(True)
        GC.SuppressFinalize(Me)
    End Sub
#End Region

End Class

问题:

  1. 如果在 GC 期间调用 Finalize() 而没有首先显式调用 object.Dispose() 则 disposing:=false 并且“if disposing...”中的代码将永远不会执行以释放托管资源 - 导致它们保留在内存中直到下一次GC通过。为什么不明确释放这些?这样做不会在第一次 GC 传递时释放更多内存,并且不会在下一次传递之前将不需要的对象留在内存中吗?
  2. 为什么在 IDisposable 类上覆盖 Finalize() 时 IDE 不生成 Dispose(false) 调用?
  3. GC 如何知道调用 Dispose(false) 并确保它是 IDE 的实现,而不是以不同方式使用 bool 参数的自定义实现?* ...如果 GC 测试它的存在并以假定某个实现的方式使用它(object.Dispose(disposing:=false)),那么 Dispose(disposing as bool) 不应该是接口成员吗?* 在两者都存在的情况下Dispose()Dispose(disposing as boolean)为什么 GC 会选择调用重载的非接口成员?

总体而言,我对具有在显式调用时执行的扩展代码路径的假定附加值感到困惑Dispose()(与具有无论是否Dispose()显式调用都执行的公共路径相反)。虽然我可以理解它是出于善意提供的,但如果Dispose()不直接调用它,除了延迟托管资源的实际释放之外,我看不到它是如何做的。从本质上讲,它似乎只能使对象图中的托管资源无法访问,将它们孤立起来直到第二次 GC 运行,而不是在已知不再需要它们的时候释放它们。

4

3 回答 3

3

你的问题有一个逻辑错误......如果Dispose()在终结器中调用,那么是的,disposedValue将是错误的,这意味着If Not Me.disposedValue Then...它将执行。传递的参数disposingtrue,因此其中的所有代码都应该可以正常执行。

编辑 (结果是终结器调用 Dispose(false))

表单上的终结器(仅在从未在表单上调用时才运行Dispose())调用Dispose(false). 原因Form是当前正在被 GC 处理。因此,MANAGED 资源(即 上的组件Form)将被收集,并且如果需要,它们自己的终结器应该调用Dispose()。只有非托管资源应该在Dispose(false).

于 2009-04-21T15:33:55.173 回答
0

“如果处置...”将永远不会执行以释放托管资源 - 导致它们保留在内存中直到下一次 GC 通过。为什么不明确释放这些?

“如果处置”将不会运行,但托管资源将在此 GC 过程中被释放(如果它们实际上有资格释放)。到您的对象最终确定时,任何其他活动对象都无法访问它,因此任何子对象都不会不符合收集条件,因为您的对象仍然引用它们。

为什么在 IDisposable 类上覆盖 Finalize() 时 IDE 不生成 Dispose(false) 调用?

在为 Finalizer 创建覆盖存根时,他们可能只是没有添加特殊情况来检查 Dispose。顺便说一句,IDE 不会自动生成终结器,因为大多数类不需要终结器。唯一应该拥有终结器的情况是您直接拥有非托管资源。Dispose(false) 路径仍然可以由拥有非托管资源的派生类调用,因此需要终结器。派生终结器的可能性也是对 GC.SuprressFinalize 的调用应该始终存在于基本 Dispose() 方法中的原因。

GC 如何知道调用 Dispose(false) 并确保它是 IDE 的实现,而不是以不同方式使用 bool 参数的自定义实现?

它没有。GC 知道 Finalize,并且只知道 Finalize。Disposable 是程序员而不是垃圾收集器的模式。这就是为什么模式要求您编写一个自己调用 Dispose(false) 的终结器。

有两条路径的原因是可以通过两种方式调用 Dispose 方法。常见的经验是显式调用,此时托管对象都存在,真正的路径会处理掉它们,释放所有非托管资源。调用终结器时将使用错误路径,此时您不能假设您的任何字段尚未调用自己的终结器。拆分路径对对象图或何时收集包含的对象没有影响。

于 2010-09-24T21:14:13.993 回答
0

当预期许多类将包装托管和非托管资源并且派生类可能将非托管资源添加到类时,创建了该模式。从那时起,Microsoft 认识到将非托管资源包装在自己的类中比将它们与其他类组合更好,并编写了 SafeHandle 类来促进这一点。使用 IDisposable 模式可能有一些很好的理由,其中不可覆盖的方法调用可覆盖的方法(例如,外层可以使用 Interlocked.Exchange 来确保仅调用一次 Dispose)。不幸的是,.net 中没有一种干净的方法可以同时覆盖和隐藏方法。否则,理想的模式是让每个 IDisposable 类公开一个新的受保护的可覆盖方法,该方法将覆盖其超类'

Blah1 类
    子处置()
        尝试
            处置1()
        最后

        结束尝试
        ' Blah1 清理这里
    结束子
    受保护的可覆盖子 Dispose1()
        ' 子级代码将覆盖这个
    结束子
结束类
Blah2 类
    继承 Blah1
    受保护的 NotOverridable 覆盖 Sub Dispose1()
        尝试
            处置1()
        最后

        结束尝试
        ' 在这里清理 Blah2
    结束子
    受保护的可覆盖子 Dispose2()
        ' 子级代码将覆盖这个
    结束子
结束类

如果不必为每个级别的处理例程指定一个新名称,则该模式会更简洁。请注意,与传统的 Dispose 模式不同,此模式将确保派生类的 Dispose 方法中引发的异常(或子类的 Dispose 方法调用其父类的失败)不会阻止父类被释放。

于 2011-01-16T22:51:57.670 回答