Click here to Skip to main content
Click here to Skip to main content

Get Delegate from Event's Subscription

, 4 Apr 2009 CPOL
Rate this:
Please Sign up or sign in to vote.
This code allows you to get a delegate that subscribed to a Control's event. The technique used is applicable to events in general.

Introduction

This code allows you to get a delegate that subscribed to a Control's event. The technique used is applicable to events in general.

Background

Recently I came across a problem. The task was to extend the functionality of some third-party control. No source code is available, all public classes are sealed. I needed to change the behavior of a button that showed SaveFileDialog and then rendered a report with default settings. There was no way to change the settings of render but to rewrite all control's toolbar.

But all I needed was to change the handler of a Click event on a control's button. To do that, one should unsubscribe old handler (-=) and subscribe a new one (+=). The question is - how to get the old handler when you don't have access to it?

To think about it, the same situation arises when a developer wants to unsubscribe from an anonymous delegate or lambda expression. There is no name to reference them (apart from storing it beforehand in a variable. But anonymous delegates, you know... should stay anonymous).

Research

First let's see the event storing background. I was to modify the behavior of a ToolStripButton so we'll dig from it.

Reflector shows that there are only two events in the ToolStripButton class. But there are a bunch of them in the base ToolStripItem class. The Click event for example is defined like:

public event EventHandler Click
{
    add { base.Events.AddHandler(EventClick, value); }
    remove { base.Events.RemoveHandler(EventClick, value); }
}

We see that there is an Events collection that stores all subscribed delegates. And the kind of event is determined by the key object - EventClick in this case:

public abstract class ToolStripItem: Component, ...
{
    ...
    internal static readonly object EventClick;
    ...
}

The Events collection (of type EventHandlerList) is stored in System.ComponentModel.Component class and has an interesting method Find:

public sealed class EventHandlerList : ...
{
    ...
    private ListEntry Find(object key);
    ...
    private sealed class ListEntry
    {
        ...
        internal Delegate handler;
        ...
    }
}

Implementation

In summary, to get access to a delegate that subscribed to the Click event, one could write something like:

control.Events.Find( control.EventClick ).handler 

But encapsulation won't allow it. Luckily there is a way to achieve that - reflection. The code will be:

var handler = (EventHandler) GetDelegate( toolStripButton, "EventClick" );
private static object GetDelegate( Component issuer, string keyName )
{
    // Get key value for a Click Event
    var key = issuer
        .GetType( )
        .GetField( keyName, BindingFlags.Static | 
		BindingFlags.NonPublic | BindingFlags.FlattenHierarchy )
        .GetValue( null );
    // Get events value to get access to subscribed delegates list
    var events = typeof( Component )
        .GetField( "events", BindingFlags.Instance | BindingFlags.NonPublic )
        .GetValue( issuer );
    // Find the Find method and use it to search up listEntry for corresponding key
    var listEntry = typeof( EventHandlerList )
        .GetMethod( "Find", BindingFlags.NonPublic | BindingFlags.Instance )
        .Invoke( events, new object[] { key } );
    // Get handler value from listEntry 
    var handler = listEntry
        .GetType( )
        .GetField( "handler", BindingFlags.Instance | BindingFlags.NonPublic )
        .GetValue( listEntry );
    return handler;
}

Available event keys (it's not only "EventClick" you know...) can be accessed with a method:

private static IEnumerable<string> GetEventKeysList( Component issuer )
{
    return
        from key in issuer.GetType( ).GetFields( BindingFlags.Static | 
		BindingFlags.NonPublic | BindingFlags.FlattenHierarchy )
        where key.Name.StartsWith( "Event" )
        select key.Name;
}

The code with the article contains a project that demonstrates all techniques described.

Conclusion

The event manipulation syntax in C# was made restrictive with a purpose. It will not break event encapsulation. It is by design and it is the right thing.

A situation when you need to access an unknown delegate that subscribed to a known event is rare. But it exists. I hope this article saved you some time when you came across such a problem.

History

  • 5th April, 2009: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Alexander Kostikov

Russian Federation Russian Federation
No Biography provided

Comments and Discussions

 
GeneralThank you for sharing... PinmemberOleg Shilo21-Jun-09 1:14 
Not that many people realize that working with events through reflection is almost black magic. You cannot even compare level of inconvenience when dealing with events and something as trivial as getting property value.
 
For example Microsoft uses one naming convention for event keys ("Event"+[eventName]) and Infragistics another ("EVENT_"+[eventName]). And all this burried in private sealed classes/members that can be penetrated only with Reflector.
 
You have to do a fair amount of research for achieving even reasonable simple tasks. Dependency injections/interceptors/aspects can be alternative but not necessarily elegant one.
 
For me all this is not so important any more as I have already spend my time discovering all what described in the article. But for one who just about to dive into Reflection+Events this article can be a life saver.
 
When it comes to potential uses, the presented technique can be used for automation of third party managed applications.
 
Cheers,
Oleg

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.141223.1 | Last Updated 5 Apr 2009
Article Copyright 2009 by Alexander Kostikov
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid