I was recently implementing some custom events, and found a couple of good (if old) articles describing how to do this efficiently using EventHandlerList:
- Manage many events easily with EventHandlerList
- Objects with dense events, but sparse usage can benefit from custom event storage.
Those articles go into why it’s nicer to deal with one EventHandlerList instead of many seperate EventHandlers, so read those for more information. For the lazy, here’s some code showing how you’re supposed to use these things:
[csharp]
public class MyClass {
private EventHandlerList Events = new EventHandlerList();
public event EventHandler MyEvent {
add { Events.AddHandler(“MyEvent”, value); }
remove { Events.RemoveHandler(“MyEvent”, value); }
}
public event EventHandler MyOtherEvent {
add { Events.AddHandler(“MyOtherEvent”, value); }
remove { Events.RemoveHandler(“MyOtherEvent”, value); }
}
protected void OnMyEvent(object sender, EventArgs e) {
EventHandler handler = (EventHandler) Events[“MyEvent”];
if (handler != null) {
handler(sender, e);
}
}
protected void OnMyOtherEvent(object sender, EventArgs e) {
EventHandler handler = (EventHandler) Events[“MyOtherEvent”];
if (handler != null) {
handler(sender, e);
}
}
}
[/csharp]
Pretty straightforward stuff. When you add an event handler to the list, you associate it with a key, and then when its time to trigger the events, you look for any handlers under the same key. The other day I was putting together something similar, and ran into some unexpected behavior with the keys. I had started by refactoring the magic strings into an enum:
[csharp]
protected enum MyEvents {
MyEvent,
MyOtherEvent
}
[/csharp]
and replaced all the strings with members of that enum. I figured this would work just fine, but the change caused my unit test to fail. Upon debugging, the EventHandlerList was always returning null in my On*Event
calls. After some more testing, the pattern became apparent: value types don’t work as keys. This was somewhat unexpected, as I’ve used enums like this in Hashtables all over the place before. After doing a little Reflectoring, the actual search for the key comes down to traversing a linked list with a simple equality test, something like this:
[csharp]
while (head != null)
{
if (head.key == key)
{
return head;
}
head = head.next;
}
[/csharp]
The culprit ends up being C#’s auto-boxing. The key
is stored as an object, so my value types are being boxed on the way in, and therefore ==
is comparing object identity, not the object values. If EventHandlerList used head.key.Equals(key)
, everything would have worked how I expected. The solution to rid myself of magic strings now becomes using static objects as my keys, so the object identities will match:
[csharp]
private static readonly object MyEventKey = new object();
private static readonly object MyOtherEventKey = new object();
[/csharp]
That pattern reminds me a lot of enums in Java before it got a enum keyword, which came on the heels of C#’s nice solution to the enumerated type problem. It’d be nice if I could use enums for their intended purpose, but cases like this make me a bit wary. Where else in the .NET framework am I going to find object identity equality where I expect to find object value equality? Is there some rational explanation for this, or is this just a bug?