【问题标题】:Why does VS2005/VB.NET implement the IDisposable interface with a Dispose(disposing as boolean) overload?为什么 VS2005/VB.NET 使用 Dispose(disposing as boolean) 重载实现 IDisposable 接口?
【发布时间】:2010-10-20 20:45:49
【问题描述】:

最近我需要将 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 运行,而不是在已知不再需要它们的时候释放它们。

【问题讨论】:

    标签: vb.net design-patterns visual-studio-2005 idisposable


    【解决方案1】:

    您的问题存在逻辑错误...如果在 Finalizer 中调用 Dispose(),那么是的,disposedValue 将为 false,这意味着 If Not Me.disposedValue Then... 将执行。为disposing 传递的参数是true,因此其中的所有代码都应该可以正常执行。

    编辑 (原来 Finalizer 调用 Dispose(false))

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

    【讨论】:

    • 感谢您发现这一点;我混淆了字段(我现在已经更正了)。您还通过说明 GC 不会调用 Dispose(false) 来部分回答我的问题,这让我想知道为什么 VS2005 完全使用这种重载。有没有 Dispose(false) 会执行的情况?它是否被 VS 排除,以便 if 我覆盖 Finalize() 然后我可以调用 Dispose(false)?看起来很有趣,因为如果我覆盖 Finalize(),IDE 不会存根任何相关代码
    • 我已经编辑了我的答案;正如您所描述的,GC 确实调用了 Dispose(false)。这样做的原因是,将使用 Dispise(true) 处理的托管组件将(或者更确切地说,应该)由 GC 单独完成和处理。
    • 有趣的东西。我注意到在 C# 中实现 IDisposable 接口不会产生重载;但是在没有重载的情况下运行代码分析(在 C# 或 VB.NET 中)确实会生成两个警告,指示您添加重载。
    • 那是因为 IDisposable 只有 Dispose() 方法。 Dispose(bool) 重载只是一个约定,由您自己实现终结器来调用适当的重载。
    【解决方案2】:

    "if disposing..." 永远不会执行 释放托管 资源——导致它们剩余 在内存中直到下一次 GC 通过。为什么 这些不会被显式释放吗?

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

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

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

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

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

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

    【讨论】:

      【解决方案3】:

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

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

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

      【讨论】:

        猜你喜欢
        • 2011-06-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-07-04
        • 2012-01-19
        • 2013-06-17
        相关资源
        最近更新 更多