Have you ever wanted to remove a handler from an event, but haven't had a reference to the object receiving the event, or perhaps you don't even know what type the object has!  .NET hides the details of events but with a bit of help from the Reflection classes, you can edit the handlers for your own purposes.

Cautionary note

In general, this is a really, really, really bad thing to do! I'm writing about it partly because it is educational, and partly because it was an interesting problem to solve.

Behind the scenes of .NET events

I'm going to be using C# throughout this post, but the comments apply equally to those of you using Visual Basic .NET.

You declare an event in C# using the event keyword. In its simplest form it looks like the following:

public class EventClass
{
  public event EventHandler MyEvent;
}

The compiler converts this into four class members:

  1. A private, instance field named MyEvent of type EventHandler (which is derived from System.MulticastDelegate)
  2. A public, instance event named MyEvent of type EventHandler (which wraps the private field)
  3. A public, instance method named add_MyEvent (to attach an event handler)
  4. A public, instance method named remove_MyEvent (to remove an attached event handler)

To manipulate the handler list you need access to the private field.  This cannot be accomplished using C# code directly.  Instead, you need to use reflection to access the field.

Getting access to the private field

To get access to the private field, you need to use the reflection classes.  In particular you need to specify that you want to bind to a non-public, instance field.  This is achieved as follows:

// Add a using statement for System.Reflection
// Assumes an instance of EventClass called e
Type t = e.GetType();
FieldInfo f = t.GetField("MyEvent",
  BindingFlags.Instance |
  BindingFlags.NonPublic);

The name of the field will match the name of the event, in this case it is MyEvent.

Manipulating the handler list

The value of the field is either null or an instance of a MulticastDelegate.  A MulticastDelegate wraps a list of System.Delegate instances.  The list is ordered and so the order should also be preserved within any manipulation.  MulticastDelegates are also immutable so we need to create a new MulticastDelegate with only those handlers we want to keep.  The process is as follows:

  1. Retrieve the current value of the private field
  2. Check whether any handlers are currently attached
  3. Get the list of Delegates currently attached
  4. Create a new, filtered list of Delegates
  5. Create a new MulticastDelegate using the filtered list of Delegates
  6. Set the value of the field to the newly created MulticastDelegate

Step 1: Retrieve the current value of the private field

Retrieving the existing value is straightforward:

object originalValue = f.GetValue(e);

Step 2: Check whether any handlers are currently attached

The value of the field will be null if no handlers are attached, so wrap the remaining steps in a conditional block:

if (originalValue != null) {
    ...
}

Step 3: Get the list of Delegates currently attached

The value must be cast to the type System.MulticastDelegate.  Then, the instance method GetInvocationList of MulticastDelegate can be used to get the list of attached handlers:

System.MulticastDelegate originalDelegate =
    (System.MulticastDelegate) originalValue;
System.Delegate[] originalHandlers =
    originalDelegate.GetInvocationList();

Step 4: Create a new, filtered list of Delegates

The following code illustrates how to remove all handlers registered by a class DelegateHacking.A.  It has not been written particularly efficiently, but it demonstrates the process:

// requires: using System.Collections.Generic;
List<System.Delegate> newHandlers =
    new List<System.Delegate>();

foreach (System.Delegate handler in originalHandlers)
{
    // compare the type of the class containing
    // the event handler with a known type
    // to be filtered
    if (handler.Method.ReflectedType.Equals(
            typeof(DelegateHacking.A)))
    {
        // don't add any matches to the list
        continue;
    }
    // add the handler to the filtered list
    newHandlers.Add(handler);
}

Step 5: Create a new MulticastDelegate using the filtered list of Delegates

You cannot create a Delegate instance directly.  Instead, you need to use the static methods of Delegate or MulticastDelegate.  In this case, the Combine method of the MulticastDelegate class is used:

System.MulticastDelegate newValue =
    System.MulticastDelegate.Combine(
        newHandlers.ToArray());

Step 6: Set the value of the field to the newly created MulticastDelegate

This final step sets the value of the field to the new delegate.  It is interesting to note that if the list of event handlers had been filtered completely, so that none were left, the value returned by MulticastDelegate.Combine would be null.

f.SetValue(e, newValue);

The manipulation in full

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

public class EventClass
{
    public event EventHandler MyEvent;
}

public class Test
{
    public static void ManipulateHandlers(EventClass e)
    {
        Type t = e.GetType();
        FieldInfo f = t.GetField("MyEvent",
            BindingFlags.Instance |
            BindingFlags.NonPublic);

        object originalValue = f.GetValue(e);
        if (originalValue != null) {

            System.MulticastDelegate originalDelegate =
                (System.MulticastDelegate) originalValue;
            System.Delegate[] originalHandlers =
                originalDelegate.GetInvocationList();

            List<System.Delegate> newHandlers =
                new List<System.Delegate>();

            foreach (System.Delegate handler 
                        in originalHandlers)
            {
                // compare the type of the class containing
                // the event handler with a known type
                // to be filtered
                if (handler.Method.ReflectedType.Equals(
                        typeof(DelegateHacking.A)))
                {
                    // don't add any matches to the list
                    continue;
                }
                // add the handler to the filtered list
                newHandlers.Add(handler);
            }

            System.MulticastDelegate newValue =
                System.MulticastDelegate.Combine(
                    newHandlers.ToArray());

            f.SetValue(e, newValue);
        }
    }
}

Full example source code

You can download the code for a full console application example here (you'll need to rename it):

Acknowledgements

Versions

  • Microsoft .NET Framework 2.0

Metadata


Bookmark with :
Digg It! DZone StumbleUpon Technorati Reddit Del.icio.us Newsvine Furl Blinklist
posted @ Monday, May 19, 2008 4:28 PM | in .NET Software Development

Comments

Gravatar
# re: .NET: Hacking events and manipulating delegates
Posted by Gene
on 7/25/2008 9:51 PM
Doesn't this work:

MulticastDelegate.RemoveAll(MyEvent, MyEvent)

I would think this would remove all of the delegates from the event's invocation list. Dunno though....we are having some problems with events and I haven't tested this yet.
Gravatar
# re: .NET: Hacking events and manipulating delegates
on 7/28/2008 9:21 AM
No, this does not do what you want.

The problem discussed in this post is the removal of handlers implemented by a specific type. You do not know the type in advance and so it is not straightforward to create a delegate with the correct signature, so you cannot use Delegate.RemoveAll.

RemoveAll is used to manipulate a delegate by removing handlers in an existing or new (Multicast)Delegate. The framework uses this behind the scenes, but it does not solve the problem illustrated here (at least not directly).

Post Comment

Title *
Name *
Email
Url
Comment *  


Please add 4 and 3 and type the answer here: