How to find registered event handlers?

Hi,
I'm trying to determine if an object has any event handlers registered on it. The reason I need this, is to not let an object to register one single event twice.


this.menu.BeforePopup += new CancelEventHandler(menu_BeforePopup); // OK
this.menu.BeforePopup += new CancelEventHandler(menu_BeforePopup); // Don't let it happen twice


 



Any help would be great




Answer this question

How to find registered event handlers?

  • Glenn Burnside

    You can only do that from within the class that provides the event, not from the outside. You have to write explicit event add and remove accessor methods where you can do any additional checks you want. Something like


    private CancelEventHandler _beforePopup;
    public event CancelEventHandler BeforePopup
    {
       add
       { 
          if (_beforePopup != null)
             foreach (Delegate d in _beforePopup.GetInvocationList()
                if (d.Equals(value)) return;
          _beforePopup += value;
       }
       remove { _beforePopup -= value; }
    }

     

    Not foolproof because 'value' could be a multicast delegate itself, but it's a start.



  • Savvas Christodoulou

    Lol well yes you're correct about the combine but  you're wrong about the Event, it is a delegate and returns a delegate. Event is just a nice little metadata about the delegate field. Just updated and runned this example, worked fine.

          public static Delegate HookDelegate(Delegate eventDelegate, Delegate delegateToHook)
            {
                bool alreadyHoooked = false;
                if (eventDelegate != null)
                {
                    foreach (Delegate d in ((Delegate)eventDelegate).GetInvocationList())
                    {
                        if (d.Equals(delegateToHook))
                        {
                            alreadyHoooked = true;
                            break;
                        }
                    }
                }

                if (!alreadyHoooked)
                    eventDelegate = Delegate.Combine(eventDelegate, delegateToHook);

                return eventDelegate;
            }
       

     In the class:

     public static event EventHandler  anEvent;

    and in main:

    anEvent = (EventHandler)HookDelegate(anEvent, new EventHandler(Do));

     

    Although some refactoring is in place, conceptually this works.



  • Aplus191

    Sorry, updated the example, now it's correct.

  • Deepak Rangarajan

    Yes, you need to know the name of the event handler, but you do not need the exact instance of the delegate. You can simply use a newly created delegate with the same method and object (i.e. this.menu_Clicked) to unhook the method. The original poster seemed concerned about not hooking the menu click event twice.

  • Kaos

    but that looks like you need to know the name of the method that has been assigned to the event handler.

     

     

     



  • Chris Erickson

    Assuming that you know the maximum number of times that an event can be registered, you could always unhook and then rehook like this:
     
    this.menu.BeforePopup -= new CancelEventHandler(menu_BeforePopup); // OK
    this.menu.BeforePopup += new CancelEventHandler(menu_BeforePopup); // OK
     
     Unhooking an event that hasn't been hooked has no effect. So as long as you know that it's been hooked at most once before (or 10 times if you repeat it 10 times), you shouldn't have a problem. Definitely faster (and easier) than resorting to reflection.


  • JustinLabenne

    > FieldInfo fi = objType.GetField("Event"+eventName,All);

    This depends on the naming convention used by the C# compiler for an event's backing delegate field. There's no guarantee that different compilers (or even different versions of the same compiler) will use the same name, and there might not even be a separate field for the event. So this is hardly a general purpose solution.

     

     



  • Marius Roets

    yes. . .

    its a little difficult. . . I posted something on the newsgoups a while back. . .

    took me a while to find it. had to get it from an archive.

     

    NOTE: Talking to Juval Lowy he suggested there was something wrong with my model if I had to do this. He was right.

    at any rate. . . here you go:

    =====================================================

    ArrayList eventData = new ArrayList();
    EventDescriptorCollection events = TypeDescriptor.GetEvents(button1);
    foreach (System.ComponentModel.EventDescriptor myEvent in events)
    {
      //Unwire the events
      EventDatum ed = EventDatum.Create(button1, myEvent);
      if (ed == null) continue;
      EventData.Add(ed);
      ed.Unwire(myComponent);
    }
    //Do something with button1 here . . .

    // now rewire . . .

    foreach(EventDatum ed in EventData)
      if (ed != null)
        ed.Wire(myComponent);
    EventData.Clear();

    =====================================

    The utility class
    ==========================

    public class EventDatum
    {
        private EventDescriptor _eventDesc;
        private Delegate _event;
        private static MethodInfo GetEventsMethod(Type objType)
        {
            MethodInfo mi = objType.GetMethod("get_Events",All);
            if ((mi ==null)& (objType.BaseType!=null))
            mi = GetEventsMethod(objType.BaseType);
            return mi;
        }

         private static EventHandlerList GetEvents(object obj)
        {
            MethodInfo mi = GetEventsMethod(obj.GetType());
            if (mi == null) return null;
            return (EventHandlerList) mi.Invoke(obj, new object[]{});
        }

        private static FieldInfo GetEventIDField(Type objType, string eventName)
        {
            FieldInfo fi = objType.GetField("Event"+eventName,All);
            if ((fi ==null)& (objType.BaseType!=null))
            fi = GetEventIDField(objType.BaseType, eventName);
            return fi;
         }

         private static object GetEventID(object obj, string eventName)
        {
            FieldInfo fi = GetEventIDField(obj.GetType(), eventName);
            if (fi ==null) return null;
            return fi.GetValue(obj);
        }

        private static BindingFlags All
        {
            get
            {
                return                 
                    BindingFlags.Public | BindingFlags.NonPublic |
                    BindingFlags.Instance| BindingFlags.IgnoreCase|
                    BindingFlags.Static;
            }
        }

        internal EventDatum(EventDescriptor desc, Delegate aEvent)
        {
            _eventDesc = desc;
            _event = aEvent;
        }

        public static EventDatum Create(object obj, EventDescriptor desc)      
        {
            EventHandlerList list = GetEvents(obj);
            if (list==null) return null;
            object key = GetEventID(obj, desc.Name);
            if (key==null) return null;
            Delegate evnt = list[key];
            if (evnt == null) return null;
            return new EventDatum(desc, evnt);
        }

        public void Wire(object obj)
        {
            _eventDesc.AddEventHandler(obj, _event);
        }

        public void Unwire(object obj)
        {
            _eventDesc.RemoveEventHandler(obj, _event);
        }
    }

     



  • Ali Jannatpour

    Couldn't you just query the InvoationList outisde as well, this would work:

            public static void HookDelegate(Delegate eventDelegate, Delegate delegateToHook)
            {
                bool alreadyHoooked = false;

                foreach (Delegate d in anEvent.GetInvocationList)
                {
                    if (d.Target == delegateToHook.Target
                        &&
                        d.Method == delegateToHook.Method)
                    {
                        alreadyHoooked = true;
                        break;
                    }
                }

                if (!alreadyHoooked)
                 Delegate.Combine(eventDelegate, delegateToHook);
            }

    And then:

        EventHandler clickHandler = new EventHandler(button_click);
        HookDelegate(button1.Click, clickHandler);



  • Eze..

    Thanks, that's right. Unfortunately, I need to do it from outside, and that's my problem. Is there any way with help of reflection....

  • Proachbass91

    Hi Patrik,

    and what is here the "anEvent" Is it the event itself or the event handler I just can't call GetInvocationList() on any of events..



  • Yeghia Dolbakyan

    ahh. . .didnt read it closely!

  • Waleedmohsen

    Indeed, that's a great code. Like Mattias said, there's an issue with GetField("Event"+eventName,All). For me, it seems that GetField(eventName,All) just works fine.

    The second issue is

    mi.Invoke(obj, new object[]{});

    it always returns an empty EventHandlerList...



  • Ramon Balboa

    >Couldn't you just query the InvoationList outisde as well, this would work:

    No it wont, because ...

    >             Delegate.Combine(eventDelegate, delegateToHook);

    you're throwing away the return value from Combine, and ...

    >    HookDelegate(button1.Click, clickHandler);

    you're not allowed to use button1.Click in this context since it's an event and doesn't return a delegate.

     



  • How to find registered event handlers?