65.9K
CodeProject is changing. Read more.
Home

Get Delegate from Event's Subscription

starIconstarIconstarIconstarIconemptyStarIcon

4.00/5 (8 votes)

Apr 5, 2009

CPOL

2 min read

viewsIcon

41201

downloadIcon

369

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