The intersection of technology and leadership

Events, Reflection and how they don’t work in C#

Although the .Net reflection APIs make some tasks easier, we’ve found recently that those related to events and their delegates have been fairly poorly designed. Our task was very simple: dynamically manipulate the event handlers attached to a set of events.

It’s easy enough to get a declaration about an event of some sort using code like:

EventInfo infoAboutEvent = someObject.GetType().GetEvent("MyEvent"); 

Given your EventInfo class it’s easy to add an a new event handler. Unfortunately going the other way around ended up much harder. Unlike things like FieldInfo that give you direct access to get the field instance from the object using GetValue(owner), finding the event field isn’t as easy. First we had to look at all the fields, and filtered out things that looked like a Delegate so we could do some useful things with them, like finding out what subscribers were hooked up to our event.

Our ultimate goal of being able to dynamically remove EventHandlers at runtime further ran into problems when we were trying to unhook methods from private events. The result of trying to unhook a handler from a private event via the EventInfo.RemoveEventHandler method results in an InvalidOperationException with a message of “Cannot remove the event handler since no public remove method exists for the event.”

One more simple use of reflection and soon we were able to automatically unhook a variety of delegates from our events regardless of their access mechanism (although using code that wasn’t as nice as we wanted). A few refactorings later and we ended up with something actually quite useful in the end.

ReflectUtil.RemoveEventHandlersFrom(
 delegate(Delegate subject) { return subject.Method.Name.StartsWith("On"); }, 
  objectWithEvent);

And here’s the class that we ended up with. Although I can’t recommend people use reflection lightly, if you need to delve into the world of reflection with events, I hope you find this example useful.

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;

public sealed class ReflectUtil
{
  private static BindingFlags PrivatePublicStaticInstance 
    = BindingFlags.NonPublic | BindingFlags.Public |
      BindingFlags.Instance | BindingFlags.Static;

  public delegate bool MatchesOnDelegate(Delegate subject);

  public static void RemoveEventHandlersFrom(
    MatchesOnDelegate matchesOnDelegate, params object[] objectsWithEvents)
  {
    foreach (object owningObject in objectsWithEvents)
    {
      foreach (DelegateInfo eventFromOwningObject in GetDelegates(owningObject))
      {
        foreach (Delegate subscriber in eventFromOwningObject.GetInvocationList())
        {
          if (matchesOnDelegate(subscriber))
          {
            EventInfo theEvent = eventFromOwningObject.GetEventInfo();
            RemoveSubscriberEvenIfItsPrivate(theEvent, owningObject, subscriber);
          }
        }
      }
    }
  }

  // You can use eventInfo.RemoveEventHandler(owningObject, subscriber) 
  // unless it's a private delegate
  private static void RemoveSubscriberEvenIfItsPrivate(
    EventInfo eventInfo, object owningObject, Delegate subscriber)
  {
    MethodInfo privateRemoveMethod = eventInfo.GetRemoveMethod(true);
    privateRemoveMethod.Invoke(owningObject,
                               PrivatePublicStaticInstance, null, 
                               new object[] {subscriber}, CultureInfo.CurrentCulture);
  }

  private static DelegateInfo[] GetDelegates(object owningObject)
  {
    List delegates = new List();
    FieldInfo[] allPotentialEvents = owningObject.GetType()
      .GetFields(PrivatePublicStaticInstance);
    foreach (FieldInfo privateFieldInfo in allPotentialEvents)
    {
      Delegate eventFromOwningObject = privateFieldInfo.GetValue(owningObject) 
        as Delegate;
      if (eventFromOwningObject != null)
      {
        delegates.Add(new DelegateInfo(eventFromOwningObject, privateFieldInfo, 
          owningObject));
      }
    }
    return delegates.ToArray();
  }

  private class DelegateInfo
  {
    private readonly Delegate delegateInformation;
    private readonly FieldInfo fieldInfo;
    private readonly object owningObject;

    public DelegateInfo(Delegate delegateInformation, FieldInfo fieldInfo, 
      object owningObject)
    {
      this.delegateInformation = delegateInformation;
      this.fieldInfo = fieldInfo;
      this.owningObject = owningObject;
    }

    public Delegate[] GetInvocationList()
    {
      return delegateInformation.GetInvocationList();
    }

    public EventInfo GetEventInfo()
    {
      return owningObject.GetType().GetEvent(fieldInfo.Name, 
        PrivatePublicStaticInstance);
    }
  }
}

5 Comments

  1. Olav Rask

    Why not use GetEvents() to retreive names of all fields and then use GetField to get each field, instead of trying to cast each field to Delegate? I would think this would be cheaper if the class has lots of fields that are not events.. (depending on how much work is involved in optaining a single field using GetField)

    Nice work non the less – got me on the right path 🙂

  2. Olav Rask

    argh.. ..should say “..to retreive names of all events..” in the first line..

  3. koolguycg

    Hi,

    The GetValue() call is not working… it returns null, any thoughts….

    Regards,
    Sourabh

  4. koolguycg

    Hi,

    Can you please tell me how to ge the second argument (objectWithEvent) in this call

    ReflectUtil.RemoveEventHandlersFrom(
    delegate(Delegate subject) { return subject.Method.Name.StartsWith(“On”); },
    objectWithEvent);

    I need this urgently… please help!!!!

    Thanks
    Kool

  5. Patrick

    The second argument is the object that you want to remove the event from. The delegate is the condition that should match before you remove the event. Here’s a test case that might be useful for you:

    public void ShouldRemoveAllEventThatMatchTheCriteria()
    {
       ClassWithEvent classWithEvent = new ClassWithEvent();
       classWithEvent.RaiseMyEvent();

       ReflectUtil.RemoveEventHandlersFrom(delegate(Delegate subject) { return subject.Method.Name.StartsWith(“On”); }, classWithEvent);

       classWithEvent.RaiseMyEvent();

       Assert.AreEqual(1, classWithEvent.OnEventHandledCount);
       Assert.AreEqual(2, classWithEvent.EventHandledNotStartingWithOnCount);
    }

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

© 2024 patkua@work

Theme by Anders NorenUp ↑