Event horizon – Understanding custom events

Introduction

In this article I’m going to try to explain what custom events are and why you should use them in your code. As you will see the term custom event in this context is not just an event you have created for your own class, but also a custom way of storing the delegates for this event.

Most of this article is intended for the VB developer, but with the exception of VB specific parts, especially found in the Background Info section, the information here also applies to C# (but with a slightly different syntax of course).

Background info

If you have used VB or any other .Net language for an extended time, you have probably created one or two user controls, or regular classes that raises events. Events in the .Net framework are implemented as properties that accepts a delegate. A delegate is similar to a function pointer in C or C++ although a delegate is strongly typed. I wont dwelve into the difference between a function pointer and a delegate in this article, since that would be an article of its own. Instead I will just simplify and say that a delegate is an address to a method that will be called by the handler (in this case its the object that exposes the event).

If you want to handle an event raised by an object you can use the AddHandler statement. But VB also has a simplified solution using the Handles keyword. If you drop a Button on a Form you could then handle its click event like this:

Private Sub Button1_Click(ByVal sender As System.Object, _
                          ByVal e As System.EventArgs) Handles Button1.Click
  'code for the click event goes here
End Sub

The above code will be called when the Click event occur for the object named Button1. This is made possible by another VB specific keyword that’s been around since VB5, the WithEvents keyword.

When you drop the button on the Form the designer will create the code that declares and initializes the component for you. The declaration of the button the designer creates looks like this:

Friend WithEvents Button1 As System.Windows.Forms.Button

You can use this syntax yourself in your code to handle events raised by objects without the need to call the AddHandler statement. However for clarity I will show the AddHandler approach as well. If the designer wouldn’t use the WithEvents keyword, you wouldn’t be able to use the Handles keyword either to handle the event.

Friend Button1 As System.Windows.Forms.Button

In this case you would need to use the AddHandler in our code.

Private Sub Form1_Load(ByVal sender As Object, _
                       ByVal e As System.EventArgs) Handles Me.Load
  AddHandler Button1.Click, AddressOf Button1_Click
End Sub

Private Sub Button1_Click(ByVal sender As System.Object, _
                          ByVal e As System.EventArgs) 'No Handles keyword
  'code for the click event goes here
End Sub

Creating your own events

It’s very easy to let your own classes/controls expose events. In most cases you would probably declare your events like this within your class:

Public Event MySpecialEvent(ByVal sender As Object, ByVal e As EventArgs)

A class that have the above will expose a MySpecialEvent event. But this is not enough. It’s when you raise the event the code in the event handlers is being executed. So whenever it is time to raise MySpecialEvent you would use code like this:

RaiseEvent MySpecialEvent(Me, New EventArgs)

If you’re coming from a VB6 background, this will seem very familiar to you.

You might call the above a custom event, since that’s what it is. My class has a custom event called MySpecialEvent which I have implemented using the technique I just demonstrated. But as I mentioned in the introduction section, custom event (in the lack of a better name) in this context is much more than this. For Custom events you need to keep track of all the event handlers that is used. More about that in the next section.

Custom events = keeping track of event handlers

There is a problem with how we created our own events in the previous section: It can become a huge waste of memory resources.

Let me again take a regular Button in a Windows Forms application as an example. The Button component exposes no less than 68 different events. If they all would be declared with the Public Event EventName syntax, it would require that 68 fields needs to be created. Lets say a Form contains 10 buttons, that is 680 fields, and in the most likely scenario 670 of these are never used since we’re probably only interested in the Click event. So what we end up with are objects that takes longer to instantiate and more usage of heap memory that can’t be reclaimed, unless you remove the control from the Form and dispose it (and how often do you do that with controls you’ve added to a Form?).

So if you create your own user control or regular class that exposes many events you need a custom event storage. There is a special collection class called EventHandlerList in the Framework, available in the System.ComponentModel namespace, which we can use to store the event handlers.

Let’s say I create a class that contains a few typical mouse events and take advantage of the EventHandlerList to store all event handlers, I would write code similar to the following.

Public Class CommonMouseEvents

  Private events As New System.ComponentModel.EventHandlerList
  Public Custom Event Click As EventHandler
    AddHandler(ByVal value As EventHandler)
      events.AddHandler("OnClick", value)
    End AddHandler

    RemoveHandler(ByVal value As EventHandler)
      events.RemoveHandler("OnClick", value)
    End RemoveHandler

    RaiseEvent(ByVal sender As Object, ByVal e As System.EventArgs)
      Dim value As EventHandler = CType(events("OnClick"), EventHandler)
      value.Invoke(sender, e)
    End RaiseEvent
  End Event

  Public Custom Event DoubleClick As EventHandler
    AddHandler(ByVal value As EventHandler)
      events.AddHandler("OnDblClick", value)
    End AddHandler

    RemoveHandler(ByVal value As EventHandler)
      events.RemoveHandler("OnDblClick", value)
    End RemoveHandler

    RaiseEvent(ByVal sender As Object, ByVal e As System.EventArgs)
      Dim value As EventHandler = CType(events("OnDblClick"), EventHandler)
      If value IsNot Nothing Then
        value.Invoke(sender, e)
      End If
    End RaiseEvent
  End Event
  'Move event declarations go here...
End Class

Here we first create an instance of the EventHandlerList class, and then I declare my events.

Notice the Custom keyword and that the event must be declared as an EventHandler or one of its child classes. Inside the event declaration you must write code for the AddHandler(), RemoveHandler(), and RaiseEvent() procedures (the stub for these are added automatically by the designer in Visual Studio).

Now, the above is just the declaration of the events and the custom code to store, remove, and invoke the event handlers. You must still raise the events in the normal fashion, using the RaiseEvent statement.

What exactly are the EventHandlerList?

In the previous section I called the EventHandlerList a collection, but that is not really true. It doesn’t implement the IEnumerable interface, it is instead implemented as a linked list. That means that even though we save a great deal of memory space, we might have a trade-off in access speed since we need to search the list from front to end for an event handler. In the case a event handler doesn’t exist we need to traverse the whole list.

So why wasn’t this list implemented as a hash table instead? The answer is pretty simple, the list will probably not be very large. Normally you will probably not use more than say 10 events (and in most cases probably less than that) on a given object, and with that small amount of data a linked list is actually much faster than a hash table.

Have fun!

kick it on DotNetKicks.compimp itShout it

Leave a Reply

Your email address will not be published. Required fields are marked *