Click here to Skip to main content
15,867,594 members
Articles / Programming Languages / C#

Get Delegate from Event's Subscription

Rate me:
Please Sign up or sign in to vote.
4.00/5 (8 votes)
4 Apr 2009CPOL2 min read 39.7K   367   25   10
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:

C#
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:

C#
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:

C#
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:

C#
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:

C#
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:

C#
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)


Written By
Russian Federation Russian Federation
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralA small problem: a lot of the keys are in private fields in base classes Pin
jnky_boy16-Sep-09 8:28
jnky_boy16-Sep-09 8:28 
GeneralRe: A small problem: a lot of the keys are in private fields in base classes Pin
Alexander Kostikov16-Sep-09 9:10
Alexander Kostikov16-Sep-09 9:10 
It's good if it works =)

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

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