.NET: Hacking events and manipulating delegates

by Stephen Horsfield 19. May 2008 15:28

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

Tags:

Software Development

Comments

7/25/2008 8:51:43 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.

Gene |

7/28/2008 8:21:37 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).

Stephen Horsfield |

9/29/2008 7:12:13 PM #

I don't understand how this worked for you as the field names of events aren't usually (if ever) the name of the event.  For example, the field name for a button click isn't "Click" it's "EventClick".  You need to pass "EventClick" into GetField to get anything meaningful.

Jeremy |

11/25/2008 2:34:18 PM #

Hi,
Thank for the article.
However, this does not work with standard lib such the windows.forms under CompactFramework. The privates fields are not present and the delegation use native methods.

Guillaume |

11/27/2008 3:01:38 PM #

I don't develop with .NET Compact Framework at present and so I don't know how it differs.  It may not be possible to apply this method.

Perhaps someone else can help?

Stephen Horsfield |

1/29/2009 9:20:59 PM #

Hi Stephen,

Thanks for the article.  As Jeremy suggested, GetField doesn't seem to work for all situations. I need to stop the event from firing completely so I'm trying to user your code to remove all subscribers, and then re-add them later. (To be specific, when I'm loading datagridview settings I don't want ColumnWidthChanged to fire).  My understanding is that if there are no subscribers, the even doesn't fire.

The problem is Dim f As FieldInfo = t.GetField("ColumnWidthChanged", BindingFlags.Instance Or BindingFlags.NonPublic Or BindingFlags.Public) returns null, where as the equiv. GetEvent works (but I'm not sure how to accomplish the equivalent of your example).

Any help or advice would be greatly appreciated.  Is there a different name I should be using for the FieldName then "ColumnWidthChanged" as Jeremy described?

Thx

Jordan F |

Powered by BlogEngine.NET 1.5.0.7
Theme by Interakting

Interakting

A full service digital agency offering online strategy, design and usability, systems integration and online marketing services that deliver real business benefits and ensure your online objectives are met.

Calendar

<<  July 2010  >>
MoTuWeThFrSaSu
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678

View posts in large calendar