14

I need to get all events from the current class, and find out the methods that subscribe to it. Here I got some answers on how to do that, but I don't know how I can get the delegate when all I have is the EventInfo.

var events = GetType().GetEvents();

foreach (var e in events)
{
    Delegate d = e./*GetDelegateFromThisEventInfo()*/;
    var methods = d.GetInvocationList();
}

Is it possible to get a delegate with the EventInfo? How?

Community
  • 1
  • 1
BrunoLM
  • 97,872
  • 84
  • 296
  • 452
  • 1
    Quoting from the highest-voted answer on your previous question: "Now I suppose you could try to find the body of the "add" handler, decompile it and work out how the event handlers are being stored, and fetch them that way... **but please don't**. You're creating a lot of work, just to break encapsulation. Just **redesign your code** so that you don't need to do this." I wholeheartedly agree. – Daniel Pryden Sep 24 '10 at 00:39

3 Answers3

14

The statement var events = GetType().GetEvents(); gets you a list of EventInfo objects associated with the current type, not the current instance per se. So the EventInfo object doesn't contain information about the current instance and hence it doesn't know about the wired-up delegates.

To get the info you want you need to get the backing field for the event handler on your current instance. Here's how:

public class MyClass
{
    public event EventHandler MyEvent;

    public IEnumerable<MethodInfo> GetSubscribedMethods()
    {
        Func<EventInfo, FieldInfo> ei2fi =
            ei => this.GetType().GetField(ei.Name,
                BindingFlags.NonPublic |
                BindingFlags.Instance |
                BindingFlags.GetField);

        return from eventInfo in this.GetType().GetEvents()
               let eventFieldInfo = ei2fi(eventInfo)
               let eventFieldValue =
                   (System.Delegate)eventFieldInfo.GetValue(this)
               from subscribedDelegate in eventFieldValue.GetInvocationList()
               select subscribedDelegate.Method;
    }
}

So now your calling code can look like this:

class GetSubscribedMethodsExample
{
    public static void Execute()
    {
        var instance = new MyClass();
        instance.MyEvent += new EventHandler(MyHandler);
        instance.MyEvent += (s, e) => { };

        instance.GetSubscribedMethods()
            .Run(h => Console.WriteLine(h.Name));
    }

    static void MyHandler(object sender, EventArgs e)
    {
        throw new NotImplementedException();
    }
}

The output from the above is:

MyHandler
<Execute>b__0

I'm sure you can jig around with the code if you wish to return the delegate rather than the method info, etc.

I hope this helps.

Enigmativity
  • 113,464
  • 11
  • 89
  • 172
  • @jcmcbeth - What part is cryptic? – Enigmativity May 19 '11 at 08:32
  • 1
    Your EventInfo to FieldInfo delegate (ei2fi) doesn't seem to work for [events with a custom add and remove](http://msdn.microsoft.com/en-us/magazine/cc163533.aspx). – Steven Jeuris Mar 23 '12 at 18:48
  • 1
    ... and [this only works for assemblies compiled with a certain compiler](http://stackoverflow.com/a/9847465/590790). – Steven Jeuris Mar 23 '12 at 23:01
  • @StevenJeuris - You may be right - I never tested for custom add and remove. Are you getting issues with VB.NET, Mono, or something else? – Enigmativity Mar 24 '12 at 08:00
  • Visual Studio 2010 and the C# compiler. Eric [clarifies what I meant by that here](http://stackoverflow.com/a/9847603/590790). – Steven Jeuris Mar 24 '12 at 13:36
  • Notice that you can only get the field from the direct class. So if you have Control->Button then you need to get the event backing field from Control – orellabac Sep 22 '17 at 21:07
5

Similarly to Enigmativity, the invocation list can be found for other classes, not just the current class...

    private void testit()
    {
        WithEvents we = new WithEvents();
        we.myEvent += new EventHandler(we_myEvent);
        we.myEvent += new EventHandler(we_myEvent2);

        foreach (EventInfo ev in we.GetType().GetEvents())
        {
            FieldInfo fi = we.GetType().GetField(ev.Name, BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy);
            Delegate del = (Delegate)fi.GetValue(we);
            var list = del.GetInvocationList();
            foreach (var d in list)
            {
                Console.WriteLine("{0}", d.Method.Name);
            }
        }
    }

    void we_myEvent(object sender, EventArgs e)
    {
    }
    void we_myEvent2(object sender, EventArgs e)
    {
    }


public class WithEvents
{
    public event EventHandler myEvent;
}

... as long as the event handlers are declared in the class as we see above. But consider the Control class where the EventHandlerList is stored in the "Events" property and each event field name begins with "Event" followed by the event name. Then there is the Form derived classes that seem to manage events yet differently. Food for thought.

Les
  • 10,335
  • 4
  • 40
  • 60
  • This works utterly brilliantly. This code is rather difficult to derive from first principles, thanks @Les. – Contango Nov 09 '18 at 09:45
4

For my case, the field value (class ToolStripMenuItem, field EventClick) frustratingly is of type object, not delegate. I had to resort to the Events property that Les mentioned in his answer, in a way I got from here. The field EventClick in this case only holds the key to the EventHandlerList stored in this property.

Some other remarks:

  • The rule fieldName = "Event" + eventName I employed will not work in every case. Unfortunately there is no common rule for linking the event name to the field name. You might try to use a static dictionary for the mapping, similar to this article.
  • I'm never quite sure about the BindingFlags. In another scenario, you might have to adjust them.

    /// <summary>
    /// Gets the EventHandler delegate attached to the specified event and object
    /// </summary>
    /// <param name="obj">object that contains the event</param>
    /// <param name="eventName">name of the event, e.g. "Click"</param>
    public static Delegate GetEventHandler(object obj, string eventName)
    {
        Delegate retDelegate = null;
        FieldInfo fi = obj.GetType().GetField("Event" + eventName, 
                                               BindingFlags.NonPublic | 
                                               BindingFlags.Static |
                                               BindingFlags.Instance | 
                                               BindingFlags.FlattenHierarchy |
                                               BindingFlags.IgnoreCase);
        if (fi != null)
        {
            object value = fi.GetValue(obj);
            if (value is Delegate)
                retDelegate = (Delegate)value;
            else if (value != null) // value may be just object
            {
                PropertyInfo pi = obj.GetType().GetProperty("Events",
                                               BindingFlags.NonPublic |
                                               BindingFlags.Instance);
                if (pi != null)
                {
                    EventHandlerList eventHandlers = pi.GetValue(obj) as EventHandlerList;
                    if (eventHandlers != null)
                    {
                        retDelegate = eventHandlers[value];
                    }
                }
            }
        }
        return retDelegate;
    }
    
Mike Fuchs
  • 12,081
  • 6
  • 58
  • 71