Reflecting over an Event

Tags: .NET, C#, CodeSnippets, Reflection

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<EventArgs> 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<EventArgs> del = delegateField.GetValue(myObj) as EventHandler<EventArgs>;

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

foreach (EventHandler<EventArgs> 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?

3 Comments

  • Evgeny said

    Thanks, However with Interop objects i always get {Dimensions:[0]} when calling: myObj.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance) even though myObj have events. :-(

  • Dan said

    Actually there is a way to reflect those events. It's just that a lot of Windows controls especially use custom add and remove methods for events to centralize them through and Events internal collection. If you refelect this property and the object constant you can get the delegates. Lutz Roeder's Reflector.NET was really useful in figuring out what windows was doing with this, take a look! -Dan

  • Martin said

    Hy, I have a similar problem. But the object from which I want to get the delegate is a Button control. As from yesterday the source of the .NET Framework is "open-source", and I had a look: Button inherits "Events" from Control, which is an EventHandlerList. But I can't see anything when using your approach (myButton.GetType().GetEvent("MyEvent")). Do you have an Idea?

Comments have been disabled for this content.