[Listeners]

The objective

Wednesday, July 23, 2003

How do you find out which methods handle a given event?

From the context where an event is declared (i.e., in the same class), it's easy: just call GetInvocationList() on the delegate reference (which is named like the event in C#, and has an "Event" postfix in VB.NET). But realistically, you're rarely coding within such a scope.

Steps I've taken

The first try is System.Reflection.EventInfo. And what you get is hook-up/unhook methods, maybe the raise method, but you don't get any field that holds the delegate. How delegate object references are managed by event-raising classes depends on the implementation.

For example, VB.NET adds a private field (referring to the delegate) called "<event-name>Event", whereas the C# compiler gives the field the same name as the event. But note that C# (as well as VB.NET 2005) gives you more control over event implementation details (such as defining Add and Remove methods), so this applies only to some cases.

In this article, I'll refer to the event implementations in components and controls only, which are different.

Classes derived from System.ComponentModel.Component keep event handler delegates in a special linked list (one per instance), each entry holding a mulicast delegate for one event (if any handlers have subscribed). The entries are looked up with a System.Object key, which is defined statically in the class that declares the event (one key per event). The keys are instance of nothing more specific than System.Object, so we need to look at the fields holding them, as see what their names are.

With reflection, you can access both the per-instance event handler list, as well as the static keys.

Note that the event handler list is also exposed to derived classes via the protected "Events" property, but again, you don't want to (and often can't) derive a class just for this inspection code.

Since these members aren't public, you need to specify the appropriate BindingFlags. The next gotcha is that you need to use two different Type objects - to get the field holding the event handler list, use the Type object for the System.ComponentModel.Component class (the field is called "events"); to get the static key fields, use the Type object of the class that defines the event (since private members are not inherited); the names vary: usually it's "Event<event-name>", but the RichTextBox class, for example, uses an all-caps spelling with names that don't always match - so you need to try it out.

The wrapper function

This functionality is wrapped up in the Gregor.Core.CReflect.TraceControlEventHandlerList method. Here is the code as of January 2003 (note that, in the meantime, the code has been refactored into the Gregor.Core.EventUtil module - check it out):

using System;
using System.ComponentModel;
using System.Reflection;
using System.Windows.Forms;
using Gregor.Core;
using Gregor.Core;

namespace Gregor.Core {

public class Reflect {

public static void TraceControlEventHandlerList(
                        Control ctl,
                        Type tpEventKeyFieldDeclarer,
                        string sEventKeyFieldName){

    Check.Control(ctl);
    Check.Reference(tpEventKeyFieldDeclarer);
    Check.Assignability(ctl, tpEventKeyFieldDeclarer);
    Check.String(sEventKeyFieldName);

    // get "events" field pointing to handler list declared in
    // System.ComponentModel.Component
    FieldInfo fiList = typeof(Component).GetField("events",
                                         BindingFlags.Instance | BindingFlags.NonPublic);
    object oList = fiList.GetValue(ctl);
    EventHandlerList list = (EventHandlerList) oList;

    // get private event key field of user-guessed name, defined in
    // user-guessed type (typically, it's static)
    FieldInfo fiKey = tpEventKeyFieldDeclarer.GetField(sEventKeyFieldName,
                                         BindingFlags.Static | BindingFlags.NonPublic);
    if(fiKey == null){
        throw new ArgumentException("Can't find field '" + sEventKeyFieldName
                        + "' in class '" + tpEventKeyFieldDeclarer.Name + "'.");
    }
    object oKey = fiKey.GetValue(ctl);
    if(oKey == null){
        throw new ArgumentException("Field '" + sEventKeyFieldName + "' is null.");
    }

    // get the multicaster from the event list
    Delegate del = list[oKey];

    // walk through the invocation list
    Delegate[] handlers = del.GetInvocationList(); // start with list's head ("del" itself)
    foreach(Delegate handler in handlers){
        Dev.Trace(handler.Method.DeclaringType.Name + "." + handler.Method.Name);
    }
}

} // class Reflect

} // namespace Gregor.Core