views:

592

answers:

2

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 existance 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.

+3  A: 

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

Adam Robinson
Thanks for spotting that; I mixed up the fields (I've corrected it now). You also partly answer my question by stating that the GC won't call Dispose(false), which makes me wonder why VS2005 uses this overload at all.Is there ever a case where Dispose(false) would execute? Is it stubbed out by VS so that *if* I override Finalize() then I can call Dispose(false)? Seems funny since the IDE doesn't stub any related code out if I override Finalize()
STW
I've edited my answer; the GC DOES call Dispose(false) as you describe. The reason for this is that managed components that would be disposed with Dispise(true) will be (or, rather, SHOULD be) individually finalized and disposed by the GC.
Adam Robinson
Interesting stuff. I've noticed that in C# implementing the IDisposable interface doesn't generate the overload; however running code analysis without the overload (in either C# or VB.NET) does generate two warnings instructing you to add the overload.
STW
That's because IDisposable only had the Dispose() method on it. The Dispose(bool) overload is a convention only, and it's up to you to implement your own finalizer to call the appropriate overload.
Adam Robinson
A: 

"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.

Gideon Engelberth