View Single Post
 
Old 09-08-2007, 09:23 PM
AtmaWeapon's Avatar
AtmaWeapon AtmaWeapon is offline
Fabulous Florist

Forum Leader
* Guru *
 
Join Date: Feb 2004
Location: Austin, TX
Posts: 9,500
Default Advanced Events Tutorial -or- Things You Probably Never Knew about Events

This is not intended to be a basic primer on events. I plan on covering some fairly advanced event syntax so I will assume you are marginally familiar with events in an attempt to keep this as brief as possible. I have a problem with verbosity that I am trying to overcome.

All code has been tested in Visual Studio 2005 Professional. My original intent was to post an appendix instructing users how to alter the code so it will compile in .NET 1.x, but after I'd already removed all partial classes it turned out that custom event syntax doesn't exist in .NET 1.X. Sorry guys, just add it to the list of reasons you should upgrade

You'll find I'm quite slim on code examples in the post; I edit the source files constantly and it's too much of a bother to keep the file with this post in sync with the code. If you want to learn the concepts, read the post. If you want to see the code, read the post and download the code. The comments in the code are pretty detailed and may even point out a thing or two that I overlooked in the post.

Anyway, let's discuss events like you've probably never seen them.

Example 1 - How Events are Implemented

Consider a class that defines a single event:
Code:
Public Class MyClass
    Public Event SomethingHappened As EventHandler
End Class
Knowing a little bit about events, one will understand that delegates are used under the hood to implement events, but how are they implemented by the compiler?

If you open the class in IL DASM, you'll find that there are quite a few things going on behind the scenes. First, you'll see a private field named after the event. Next, you'll notice an add_SomethingHappened and remove_SomethingHappened method, along with a weird structure that ties the event to these methods. Basically, under the hood events are implemented as if you had written the following code:
Code:
Private SomethingHappened As EventHandler

<MethodImpl(MethodImplOptions.Synchronized)> _
Public Sub add_SomethingHappened(ByVal value As EventHandler)
    SomethingHappened = CType([Delegate].Combine(SomethingHappened, value), EventHandler)
End Sub

<MethodImpl(MethodImplOptions.Synchronized)> _
Public Sub remove_SomethingHappened(ByVal value As EventHandler)
    SomethingHappened = CType([Delegate].Remove(SomethingHappened, value), EventHandler)
End Sub
I don't expect you to just take my word for it, so take a look at Example1.zip and see it in action. Example 1 pretty straightforward so I won't waste time discussing it.

Why the Implementation Details are Important

I won't even waste time telling you to not implement your events by writing code as above; you can't use the AddHandler syntax and it's a lot more work than the single line of code. But it is very important to see how the compiler translates that line of code for two reasons that are the key to advanced event design:
  • Every event you define introduces a delegate field to your class. If you have a class like Control with lots of events, you will be wasting lots of memory for each of these fields (even if they hold Nothing, they are taking heap memory). There is a solution, but I will defer discussion of this problem until later.
  • The synchronization method used by the built-in compiler is OK for small classes with few subscribers, but there are several problems with this model for classes with several events or several subscribers. I will discuss this problem and its solution in detail first.

Threading Concerns with the Default Event Implementation

If you take a look at the behavior that MethodImlpOptions.Synchronized will cause, you will see that since add_xxx and remove_xxx are instance methods, the method will lock on the instance. What this means is the methods behave as if they were implemented like so:
Code:
Public Sub add_SomethingHappened(ByVal value As EventHandler)
    SyncLock Me
        SomethingHappened = CType([Delegate].Combine(SomethingHappened, value), EventHandler)
    End SyncLock
End Sub
This ensures only one thread can access either method at a time, but what happens if there are two events? Both event add/remove methods will lock on the instance, so if two threads try to subscribe to different events at once, one will have to wait. What happens if you have 70 events like Control? Even worse, what if your class is the middle class of a framework, and perhaps hundreds of classes will need to subscribe and unsubscribe to its events? Odds are you will spend a lot of time blocking threads and performance will be severely degraded.

According to Richter, there's a more subtle problem that has to do with a bug in the framework as well. Normally, these locks will be on an instance, so if there are multiple instances of your class you won't see many ill effects. However, if the type is loaded domain neutral, a bug in the CLR causes the lock to be applied to the type rather than the instance. This means that if two instances of your application have an instance of your type each, both instances will block when one's events are being subscribed or unsubscribed. Now imagine your type is a business object that might have hundreds of instances in a server application. Yikes.

There is a solution! In addition to the standard event syntax, the VB language designers provided a more advanced syntax they call "Event Properties". I wanted to discuss them here, but I believe it will make more sense if I discuss the memory problem next, then illustrate the solution to both. It will be clear why this order must be followed later.

Memory Concerns with the Default Event Implementation

As previously stated, the default event implementation declares a private delegate field for every event you declare. If you implement several events, which is common in commercial controls, this can add up to a lot of wasted memory. There's no getting around the limitation that a delegate field must exist, but consider how many events you tend to subscribe to from a particular control; my guess is there are maybe 3 or 4 events from each control you are actually interested in and the rest are unused. Wouldn't it be nice if you could just not declare a delegate field for an event if no classes had subscribed to the event?

The language designers thought so too, and provided Event Properties and a class or two to help. Let's discuss Event Properties now.

Event Properties: The Solution to (almost) Everything

Event properties are the solution to the problems with the default implementation of events. An event property starts off as simply a more complicated-looking version of the event syntax:
Code:
Public Custom Event SomethingHappened As EventArgs
    AddHandler(ByVal handler as EventArgs)

    End AddHandler

    RemoveHandler(ByVal handler as EventArgs)

    End RemoveHandler

    RaiseEvent(ByVal sender As Object, ByVal e as EventArgs)

    End RaiseEvent
End Event
Each block is executed when the appropriate keyword is used for this event. Now that you have full control over the event's implementation, you are able to solve each of the problems that we have described.

Example 2 - Multiple events

The classes in Example 2 solve the problem of the allocation of multiple delegate fields per instance. This can be accomplished via the System.ComponentModel.EventHandlerList class, but there's a catch here. The EventHandlerList class uses a linked list as its underlying structure, which means its performance degrades as more items are added. Since the problem we are trying to solve is "a lot of events", its use here is simply to keep the new concepts introduced by the example to a minimum. An appendix project will use a custom Dictionary class that overcomes this hurdle.

Really most of the explanation is in the comments. You may ask if declaring a key for each event as the code does is the same problem as before, but the answer is since they are static (Shared), only one instance of each key is ever created. This will come up in the next example.
__________________
.NET Resources
My FAQ threads | Tutor's Corner | Code Library
I would bet money 2/3 of .NET questions are already answered in one of these three places.
Reply With Quote