Why Won’t You Go Away?!

All .NET developers should know that .NET is pretty reference-happy.  There’s an interesting phenomena with Structures and Classes that I can illustrate later, but the issue I wanted to point out here involves references and garbage collection.  As we know, .NET manages object cleanup through the garbage collector when there are no more references to the object.  This is kind of nice because you can let objects just fall out of scope and the GC will take care of everything.  That is, if nothing else has a reference to those objects.

One thing to pay special attention to is event handlers.  These create references and can keep objects alive MUCH longer than you want; maybe for the running life of the application.  As a potential scenario, you have a form with a form-level event.  That form has a method that creates some objects that listen for that event.  The method does its stuff and finishes.  You call the method again and suddenly you have twice as many responses to the event.  The objects you set up from the first run still exist and listen for that event.

Here’s some demo code to illustrate this.

The class that responds to the event:

Public Class HangingObject

    Public Sub SayHello()
        MsgBox("Hello from " & Me.GetHashCode.ToString)
    End Sub

End Class

The method that creates the objects and raises the event:

    Private Sub ShowHanging()
        Dim o As HangingObject

        For i As Integer = 1 To 5
            o = New HangingObject
            AddHandler Me.SayHelloEvent, AddressOf o.SayHello

        Next

        RaiseEvent SayHelloEvent()

    End Sub

And the form-level event:

Public Event SayHelloEvent()

So if you call ShowHanging, you get 5 Messageboxes.  If you call it again, you get 10, and so on.

The reason for this is the AddHandler statement.  AddHandler creates a reference to the instance of the object “o” and stores it with the form-level event SayHelloEvent.  When do these references get removed?  When the form is disposed.  If that form is the main form of the application, that will be when the application ends.

Can you get around this?  Maybe by implementing Dispose and disposing the objects?

Public Class HangingDisposableObject
    Implements IDisposable

    Public Sub SayHello()
        MsgBox("Hello from " & Me.GetHashCode.ToString)
    End Sub

    Private disposedValue As Boolean
    Protected Overridable Sub Dispose(ByVal disposing As Boolean)
        If Not Me.disposedValue Then
            If disposing Then
            End If

        End If
        Me.disposedValue = True
    End Sub

    Public Sub Dispose() Implements IDisposable.Dispose
        Dispose(True)
        GC.SuppressFinalize(Me)
    End Sub

End Class
    Private Sub ShowHanging()
        Dim o As HangingDisposableObject

        For i As Integer = 1 To 5
            o = New HangingDisposableObject
            AddHandler Me.SayHelloEvent, AddressOf o.SayHello
            o.Dispose()
        Next

        RaiseEvent SayHelloEvent()
    End Sub

You’d be surprised.  You still get 5 Messageboxes even though all five were Disposed inside the loop.  Ah, but the garbage collector hasn’t run yet.  So let’s force it to do a collection.

    Private Sub ShowHanging()
        Dim o As HangingDisposableObject

        For i As Integer = 1 To 5
            o = New HangingDisposableObject
            AddHandler Me.SayHelloEvent, AddressOf o.SayHello
            o.Dispose()
        Next

        GC.Collect()
        RaiseEvent SayHelloEvent()
    End Sub

Still surprised?  You shouldn’t be.  The GC won’t collect (Finalize) the objects because there is still a reference to that object (the event handler).  So how do you manage this?  You have to call RemoveHandler to remove the reference.  And that means you have to keep your own references to the objects until you’re done with them and you do the cleanup yourself.

    Private Sub ShowHanging()
        Dim oCollection As New Generic.List(Of HangingObject)
        Dim o As HangingObject

        For i As Integer = 1 To 5
            o = New HangingObject
            AddHandler Me.SayHelloEvent, AddressOf o.SayHello
            oCollection.Add(o)

        Next

        RaiseEvent SayHelloEvent()

        For Each o In oCollection
            RemoveHandler Me.SayHelloEvent, AddressOf o.SayHello
        Next

    End Sub

So IDispose is not the answer.  Keeping track of your objects and the references they hold is the answer.  And that should be the obvious answer anyway.