C# How to find if an event is hooked up
Asked Answered
E

5

42

I want to be able to find out if an event is hooked up or not. I've looked around, but I've only found solutions that involved modifying the internals of the object that contains the event. I don't want to do this.

Here is some test code that I thought would work:

// Create a new event handler that takes in the function I want to execute when the event fires
EventHandler myEventHandler = new EventHandler(myObject_SomeEvent);
// Get "p1" number events that got hooked up to myEventHandler
int p1 = myEventHandler.GetInvocationList().Length;
// Now actually hook an event up
myObject.SomeEvent += m_myEventHandler;
// Re check "p2" number of events hooked up to myEventHandler
int p2 = myEventHandler.GetInvocationList().Length;

Unfort the above is dead wrong. I thought that somehow the "invocationList" in myEventHandler would automatically get updated when I hooked an event to it. But no, this is not the case. The length of this always comes back as one.

Is there anyway to determine this from outside the object that contains the event?

Enshroud answered 15/7, 2009 at 5:16 Comment(0)
T
54

There is a subtle illusion presented by the C# event keyword and that is that an event has an invocation list.

If you declare the event using the C# event keyword, the compiler will generate a private delegate in your class, and manage it for you. Whenever you subscribe to the event, the compiler-generated add method is invoked, which appends the event handler to the delegate's invocation list. There is no explicit invocation list for the event.

Thus, the only way to get at the delegate's invocation list is to preferably:

  • Use reflection to access the compiler-generated delegate OR
  • Create a non-private delegate (perhaps internal) and implement the event's add/remove methods manually (this prevents the compiler from generating the event's default implementation)

Here is an example demonstrating the latter technique.

class MyType
{
    internal EventHandler<int> _delegate;
    public event EventHandler<int> MyEvent;
    {
        add { _delegate += value; }
        remove { _delegate -= value; }
    }
}
Tall answered 15/7, 2009 at 5:23 Comment(1)
One clarification that I was't certain of before I tried it is that the compiler-generated delegate has the same name as the event you declared in your code (or at least mine did).Noelnoelani
P
68

If the object concerned has specified the event keyword, then the only things you can do are add (+=) and remove (-=) handlers, nothing more.

I believe that comparing the invocation list length would work, but you need to be operating inside the object to get at it.

Also, keep in mind that the += and -= operators return a new event object; they don't modify an existing one.

Why do you want to know if a particular event is hooked up? Is it to avoid registering multiple times?

If so, the trick is to remove the handler first (-=) as removing a handler that's not there is legal, and does nothing. Eg:

// Ensure we don't end up being triggered multiple times by the event
myObject.KeyEvent -= KeyEventHandler;
myObject.KeyEvent += KeyEventHandler;
Plaice answered 15/7, 2009 at 5:29 Comment(2)
if you do += KeyEventHandler more than once, will -= KeyEventHandler remove all or just the last, just the first?Zingaro
-= will remove one; I don't know of any way to work out which one, given they're all equal.Plaice
T
54

There is a subtle illusion presented by the C# event keyword and that is that an event has an invocation list.

If you declare the event using the C# event keyword, the compiler will generate a private delegate in your class, and manage it for you. Whenever you subscribe to the event, the compiler-generated add method is invoked, which appends the event handler to the delegate's invocation list. There is no explicit invocation list for the event.

Thus, the only way to get at the delegate's invocation list is to preferably:

  • Use reflection to access the compiler-generated delegate OR
  • Create a non-private delegate (perhaps internal) and implement the event's add/remove methods manually (this prevents the compiler from generating the event's default implementation)

Here is an example demonstrating the latter technique.

class MyType
{
    internal EventHandler<int> _delegate;
    public event EventHandler<int> MyEvent;
    {
        add { _delegate += value; }
        remove { _delegate -= value; }
    }
}
Tall answered 15/7, 2009 at 5:23 Comment(1)
One clarification that I was't certain of before I tried it is that the compiler-generated delegate has the same name as the event you declared in your code (or at least mine did).Noelnoelani
M
17

It can be done, but it takes some hackery... as mentioned above the compiler generates the implementation of the event, including its backing field. Reflection lets you retrieve the backing field by name, and once you have access to it you can call GetInvocationList() even though you're outside the class itself.

Since you're asking to use reflection to get the event by name I assume you're also using reflection to get the Type by name--I'm whipping up an example that will show how to do it.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Reflection;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            string typeName = "ConsoleApplication1.SomeClass, ConsoleApplication1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null";
            string eventName = "SomeEvent";

            Type declaringType = Type.GetType(typeName);
            object target = Activator.CreateInstance(declaringType);

            EventHandler eventDelegate;
            eventDelegate = GetEventHandler(target, eventName);
            if (eventDelegate == null) { Console.WriteLine("No listeners"); }

            // attach a listener
            SomeClass bleh = (SomeClass)target;
            bleh.SomeEvent += delegate { };
            //

            eventDelegate = GetEventHandler(target, eventName);
            if (eventDelegate == null)
            { 
                Console.WriteLine("No listeners"); 
            }
            else
            { 
                Console.WriteLine("Listeners: " + eventDelegate.GetInvocationList().Length); 
            }

            Console.ReadKey();

        }

        static EventHandler GetEventHandler(object classInstance, string eventName)
        {
            Type classType = classInstance.GetType();
            FieldInfo eventField = classType.GetField(eventName, BindingFlags.GetField
                                                               | BindingFlags.NonPublic
                                                               | BindingFlags.Instance);

            EventHandler eventDelegate = (EventHandler)eventField.GetValue(classInstance);

            // eventDelegate will be null if no listeners are attached to the event
            if (eventDelegate == null)
            {
                return null;
            }

            return eventDelegate;
        }
    }

    class SomeClass
    {
        public event EventHandler SomeEvent;
    }
}
Mg answered 28/10, 2009 at 20:5 Comment(5)
I found this answer quite helpful even though the GetEventHandler() method came back with a null eventField. (I suspect this is related to me passing a Castle-based dynamicproxy into the routine instead of the proxied object.) I am/was in the process of demonstrating how dynamicproxies could be used to "automagically" implement INotifyPropertyChanged.Preestablish
You could just return eventDelegate from the GetEventHandler method without doing that last null check.Redon
I get an error saying "Unable to cast the object of type 'CallStatechanged' to type 'System.EventHandler'" CallStateChanged is the name of my eventDendro
GetField returns null. GetEvent returns the desired event but doesn't allow GetValue to be called as EventInfo doesn't contain this method.Indeterminate
Casting to EventHandler is not general enough, I suggest using return (Delegate)eventField.GetValue(classInstance)Cigarillo
P
6

You should be able to get the invocation list via the "event". Roughly, it will be something like..

public delegate void MyHandler;
public event MyHandler _MyEvent
public int GetInvocationListLength()
{
   var d = this._MyEvent.GetInvocationList(); //Delegate[]
   return d.Length;
}
Pondweed answered 15/7, 2009 at 5:45 Comment(1)
This will only work from inside the class where the event is declared; he's trying to do it outside.Minneapolis
B
0

I used your example and modified it a little bit. registering an event handler increases the number of invocations. even when using two different callback methods (as shown here) or using the same callback method.

private void SomeMethod()
{
    // Create a new event handler that takes in the function I want to execute when the event fires
    var myEventHandler = new EventHandler(OnPropertyChanged);

    // Get "p1" number events that got hooked up to myEventHandler
    int p1 = myEventHandler.GetInvocationList().Length; // 1

    // Now actually hook an event up
    myEventHandler += OnPropertyChanged2;

    // Re check "p2" number of events hooked up to myEventHandler
    int p2 = myEventHandler.GetInvocationList().Length; // 2

    myEventHandler.Invoke(null, null); 
// each of the registered callback methods are executed once. 
// or if the same callback is used, then twice.
}

private void OnPropertyChanged2(object? sender, EventArgs e)
{}
private void OnPropertyChanged(object? sender, EventArgs e)
{}

As others already mentioned, the access to eventhandler.GetInvocationList is limited to the class itself, you need to expose a property or method to retrieve the delegate list.

Like this:

protected Delegate[]? GetInvocations() => PropertyChanged?.GetInvocationList();

depending on your usage make it protected, internal or both.

Base answered 15/12, 2020 at 12:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.