Reflecting over an Event

A coworker (Hey, Evgeny!) came to me with a puzzler today. He has an object that exposes an event, and he wants to know whether he's already subscribed to that event.

Two things immediately came to mind – the first was that he really shouldn't be doing that, and the second was to suggest that if the reason for this is to avoid multiple registrations, he can always call -= and then += afterwards, thus making sure he's subscribed only once. Having established that, I got down to thinking about it.

I started checking out what reflection can do for me. I could easily get the EventInfo object for the event using:

myObj.GetType().GetEvent(“MyEvent”);

but that proved fruitless – I could get the Add and Remove MethodInfos, add a new handler and even get the delegate type for that event, but not the actual instance of the delegate. So I started thinking again.

An event in the .NET Framework is basically a wrapper around a multicast delegate. Just like a property can be a wrapper around a private data field. Rather than exposing the delegate directly, we expose a more limited subset of functionality (subscribe and unsubscribe) rather than allow my consumers to mess with my internal delegate list.

By that logic, it stands to reason that there should be an internal, private member holding the delegate itself. Probably having the same name as the event. A quick check confirmed that:

myObj.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance):
{Dimensions:[1]}
[0]: {System.EventHandler`1[System.EventArgs] MyEvent}

Right. EventHandler is my delegate type (that ugly format is the internal representation of generic types) and MyEvent is the name of the event. Using this, it's a piece of cake to get the value of the field and cast it to the multicast delegate:

FieldInfo delegateField = myObj.GetType().GetField(“MyEvent”, BindingFlags.NonPublic | BindingFlags.Instance);
EventHandler del = delegateField.GetValue(myObj) as EventHandler;

And once we had that – it's a simple matter of iterating the invocation list and seeing whether I've registered already:

foreach (EventHandler handler in del.GetInvocationList())
{
    if (handler.Target == this)
        return true;
}.

Now, I'm not saying this is a good technique – it's better not to reach a point where I don't know if I've registered or not. But still, it's good to know.

I've attached the full code listing, which is more complete, has comments and a few checks so we don't crash. Enjoy.

UPDATE: This solution is all nice and well, but as Evgeny pointed out in the comments below, this doesn't work with COM Objects. In fact, Reflection as a whole seems to be very limited, always returning __COMObject as the reflected type. Anyone know how to get more information from the RCW?

Leave a Reply

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.