Why does VS2005/VB.NET implement the IDisposable interface with a Dispose(disposing as boolean) overload?

南笙酒味 提交于 2020-01-04 13:30:12

问题


Recently I needed to compare a suggested pattern for IDisposable and object finalization with the auto-generated one we which VS2005/VB.NET provide. We have used the auto-generated one a fair bit, but after looking it the two side by side I had a number of questions about the VB.NET implementation...

For reference, here is the IDE's implementation:

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

Questions:

  1. If Finalize() is called during GC without object.Dispose() being explicitly called first then disposing:=false and the code within "if disposing..." will never execute to free the managed resources--resulting in them remaining in memory until the next GC pass. Why wouldn't these be explicitly freed? Wouldn't doing so free more memory on the first GC pass and not leave unneeded objects in memory until the next pass?
  2. Why doesn't the IDE generate the Dispose(false) call when overriding Finalize() on an IDisposable class?
  3. How would the GC know to call Dispose(false) and ensure that it is the IDE's implementation and not a custom implementation which uses the bool parameter in a different manner? * ...and shouldn't Dispose(disposing as bool) be an interface member if the GC tests for its existence and uses it in a manner that assumes a certain implementation (object.Dispose(disposing:=false))? * In the presence of both Dispose() and Dispose(disposing as boolean) why would the GC ever opt to call the overloaded, non-interface member?

Overall I am confused by the supposed added value of having an extended code-path that executes when Dispose() is called explicitly(as opposed to having a common path that is executed regardless of whether or not Dispose() was called explicitly). While I can appreciate that it is provided with good intentions I can't see how it does anything other than delay the actual release of managed resources if Dispose() isn't called directly. In essence it seems to only work to make the managed resources unreachable in the object graph, orphaning them until the 2nd GC run rather than freeing them at a point where they are known to be no longer needed.


回答1:


Your question has a logical error...if Dispose() is called within the Finalizer, then yes, disposedValue will be false, which means that If Not Me.disposedValue Then... will execute. The parameter passed for disposing is true, so all of the code within there should execute just fine.

Edit (turns out the Finalizer calls Dispose(false))

The Finalizer on the form (which only runs if Dispose() is never called on the Form) invokes Dispose(false). The reason for this is that the Form is currently being GC'ed. As a result, MANAGED resources (ie, components on the Form) will get collected and their own Finalizers should invoke Dispose() if required. Only unmanaged resources should be released in Dispose(false).




回答2:


"if disposing..." will never execute to free the managed resources--resulting in them remaining in memory until the next GC pass. Why wouldn't these be explicitly freed?

The "if disposing" will not run, but the managed resources will be freed in this GC pass (if they are in fact eligible for freeing). By the time your object is being finalized, it cannot be reached by any other live objects, so any child objects will not be ineligible for collection because your object still refers to them.

Why doesn't the IDE generate the Dispose(false) call when overriding Finalize() on an IDisposable class?

They probably just didn't add the special case to check for Dispose when creating the override stub for Finalizer. As an aside, the IDE does not automatically generate a finalizer, because most classes do not need a finalizer. The only time you should have a finalizer is if you directly own an unmanaged resource. The Dispose(false) path could still be called by a derived class that does own an unmanaged resource and thus needs a finalizer. The possibility of a derived finalizer is also why the call to GC.SuprressFinalize should always be there in the base Dispose() method.

How would the GC know to call Dispose(false) and ensure that it is the IDE's implementation and not a custom implementation which uses the bool parameter in a different manner?

It doesn't. The GC knows about Finalize, and only Finalize. Disposable is a pattern for the programmer not the garbage collector. That's why you are required by the pattern to write a finalizer that calls Dispose(false) yourself.

The reason to have two paths is that the Dispose method could be called two ways. The common experience is to call it explicitly, at which point the managed objects all exist, and the true path will dispose of them and free any unmanaged resources. The false path will be used when the finalizer is called, at which point you cannot assume that any of your fields have not already had their own finalizers called. The split path has no effect on the object graph or when the contained objects will be collected.




回答3:


That pattern was created when it was expected that many classes would wrap both managed and unmanaged resources, and that derived classes might add unmanaged resources to a class. Since then, Microsoft has recognized that it's better to wrap unmanaged resources in their own classes than to combine them with other classes, and has written the SafeHandle class to facilitate that. There may be some good reasons to have an IDisposable pattern where a non-overridable method calls an overridable one (e.g. the outer layer could use Interlocked.Exchange to ensure Dispose is only called once). Unfortunately, there's no clean way in .net for a method to be simultaneously overridden and shadowed; otherwise the ideal pattern would be for each IDisposable class to expose a new protected overridable method which would override its Superclass' method:

Class Blah1
    Sub Dispose()
        Try
            Dispose1()
        Finally

        End Try
        ' Blah1 cleanup here
    End Sub
    Protected Overridable Sub Dispose1()
        ' Child-level code will override this
    End Sub
End Class
Class Blah2
    Inherits Blah1
    Protected NotOverridable Overrides Sub Dispose1()
        Try
            Dispose1()
        Finally

        End Try
        ' Blah2 cleanup here
    End Sub
    Protected Overridable Sub Dispose2()
        ' Child-level code will override this
    End Sub
End Class

The pattern would be cleaner if one didn't have to give each level's dispose routine a new name. Note that unlike the conventional Dispose pattern, this pattern would ensure that an exception thrown in a derived class' Dispose method (or a failure of the child's Dispose method to call it's parent's) would not prevent the parent class from being disposed.



来源:https://stackoverflow.com/questions/773165/why-does-vs2005-vb-net-implement-the-idisposable-interface-with-a-disposedispos

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!