views:

2142

answers:

5

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?

+9  A: 

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; }
    }
}
Steve Guidi
+1  A: 

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;
Bevan
A: 

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;
}
This will only work from inside the class where the event is declared; he's trying to do it outside.
Pavel Minaev
+1  A: 

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;
 }
}
STW
A: 

I know this question has been answered, but I am curious why the following would not work:

public class MyType
 {
     public event EventHandler<SomeEventArgs> SomeEvent;

     public bool SomeEventIsWired 
     { 
         get { return SomeEvent != null; } 
     }
 }
Damon