Uneventful

After reading the post about events and AddHandler and tracking references and hanging objects, you might be a little concerned about using events at all, maybe because of the added code or the housekeeping.  If so, you can consider another way of doing event-type behavior using Delegates.

Delegates can be used many different ways.  This particular way might be a little different than what you’ve previously seen.  The general concept is to keep a variable holding all the delegates to be called for an event.  This variable (as a delegate itself) can be invoked at any time, and more importantly, can be cleared at any time.

Reusing some old code from the previous post, we still have our HangingObject class and we now have this method to build the objects and call their internal method:

    Private Sub UsingDelegate()
        Dim o As HangingObject

        HelloList = [Delegate].RemoveAll(HelloList, HelloList)
        
        For i As Integer = 1 To 5
            o = New HangingObject
            HelloList = [Delegate].Combine(HelloList, New SayHelloDelegate(AddressOf o.SayHello))

        Next

        HelloList.DynamicInvoke(Nothing)

    End Sub

And at the form level, we have the delegate sub definition and the delegate variable holding all the calls.

    Public Delegate Sub SayHelloDelegate()
    Dim HelloList As [Delegate] = Nothing

So what’s different?  We’ve changed the event declaration to a Delegate Sub declaration; we’ve added a variable to hold all the event subscriptions; instead of using AddHandler, we use Delegate.Combine to register the event subscription; and instead of RaiseEvent, we use DynamicInvoke.  There’s not much more or less, but everything is different.  The list of subscribers is accessible now through the variable HelloList, which is a huge benefit.

The two interesting parts of this code are the shared methods on the Delegate class: Combine and RemoveAll.  The delegate itself (HelloList) contains a list of invocation targets, similar to subscribers of an event.  The Combine method merges the specific SayHelloDelegate invocation list with the generic HelloList invocation list, resulting in one list of all the targets.  Calling the DynamicInvoke method performs a .Invoke on all the delegates in the invocation list.  Simple and magical.

However, because HelloList is declared at the form level, it persists between calls and can suffer the same issues as the AddHandler method.  The nice thing is that you can clear the invocation list by using RemoveAll, or you can just set the variable to Nothing.  If HelloList was defined within the method instead of the form, it would be cleared at the end of the method, unlike the AddHandler method, where the event is declared at the form level.

It’s good to know a lot of different ways to do something, just in case.  Another tool in the coding toolbox.

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.

Delegatorial Rambling

I see in a sister blog a planned post regarding Delegates in .NET. I have used delegates before, and I think I have seen them misused before. But remaining on the positive, I’ll describe what the delegate capability did for me.

In an application I was writing, I made the decision to extract the product search form from the main UI application; my thought was that it would be a common element that could be used in many different applications in the long-range-planned application suite. My choice was a good one, as I did use it in multiple applications, but it did get modified along the way.

The first version was simple: you search for products, you highlight the product(s) you want, and click "Select". The search form would raise an event that the calling form would handle and process. The search form also had a button to view product details. You could highlight a product and click "View Details" to bring up another form with the product information.

This worked great until I wanted to use it in another part of the same application. In this new section, there was no "Select" function needed, only "View Details". One choice I pondered was making the Select button Public instead of Friend (remember this code is in a shared DLL now). Then the calling form could enable or disable it. I also considered making a Boolean property like HideSelect. That seemed pretty tacky. But my overall goal with designing the code was that it do what the programmer tells it and do the most obvious thing by default.

Going with that mindset, I decided the Select button will do nothing unless you give it something to do. Goodbye Event, hello Delegate. I dropped the public event and created a delegate sub that matched the signature of my old event. Then I created a private variable to hold the delegate and created a public property to Get/Set the delegate.

What benefits did this gain? I was then able to see if the calling form needed the Select button. If the private variable was Nothing, I disabled the Select button. If the private variable was a valid instance, I invoked it when the Select button was clicked. When I was using events, I couldn’t tell if anyone was subscribed to the event so I couldn’t take any action on the Select button.

Back in the calling form, the code change was minimal. Instead of doing an AddHandler statement, I created a new instance of the delegate in the search form using AddressOf in the constructor and set it to the public property on the search form. The target method that handled the event never changed.

That’s one way I used a delegate. The other way I used it was with the View Details button. For another application, the View Details was slightly different. In brief, the application needed to handle the display of the product instead of letting the search form do it. Again, I created a delegate sub, private variable and public property. In the click event of the View Details button, I checked to see if the delegate was Nothing. If it was Nothing, I performed the default action and displayed the product in the typical manner. If it was not nothing, I invoked the delegate instead of doing the default display. In this way, I was able to provide an override for default behavior.

Without using delegates, how could that have been done as easily? As I was determining how do implement the override feature, I considered using events, but that means that every calling form would have to handle that event in order to implement the default display action. That went against my design philosophy. I could have used a Boolean property, but…bleh. I insisted to myself that I would not have any shared library that required performing steps in a rigid fashion. Like, oh, the code bombed out. I forgot to call InitControls2() after setting the base properties. Yuck.

So that’s my delegate story. In summary, I used Delegates instead of Events because I was able to test to see if they were needed using IsNothing() and they provided me with a way to optionally inject a call to a remote codebase (that codebase being the calling form). Sorry for no code samples. I don’t have the code with me right now.