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

Using Reflection to Manage Event Handlers

Rate me:
Please Sign up or sign in to vote.
4.90/5 (7 votes)
26 Oct 2010CPOL4 min read 39.2K   500   23   3
How to wire up delegates to events using reflection

Introduction

This article shows a solution to the problem of programmatically attaching delegate handlers to an object's events.

Background

While building a prototype for an application framework, I came across a situation where I was creating a host process for multiple plug-in components. In the end-user implementation of these plug-ins, they would be exposing events to the rest of the framework. I wanted to provide both the option of tight-coupling to these events (manually adding handlers via += new ...) and loose-coupling to these events (through a publish/subscribe event-bus mechanism).

I wanted to make the publishing of events as transparent as possible to the plug-in developers without having to dictate too much of their implementation. I decided I wanted to be able to support transparent publishing of events to the event bus for any plug-in that decorated its events with a particular attribute (PubEventAttribute).

Using the Code

I wanted plug-in implementers to simply decorate their classes as shown:

C#
public class MyClass
{
	...

	[PubEvent]
	public event Action<int> Event1;
}

The loading of the plug-ins in my framework would use reflection at initialization time to inspect the plug-in's instance for this attribute on its events. When an event with this attribute is discovered, a delegate would be programmatically attached to the event that would publish the results of the event onto an event bus.

Points of Interest

All of this elegant designing on my part hit a road-block once I realized I had no idea how to generate a delegate for some arbitrary event. I realized I could narrow down the supported events to a specific type by limiting them to the Action<...> generic classes.

By narrowing down the format of the supported events, it occurred to me that I could simply match the Action<...> events to precompiled handlers that were themselves generic classes (called ActionPublisher<...>) that expose a public method matching the signature of the event.

Using the single parameter version of Action<...> as an example, I created a matching class:

C#
public class ActionPublisher<T>
{
	public ActionPublisher( object sender, string topic )
	{
		_sender = sender;
		_topic = topic;
	}

	private object _sender;
	private string _topic;

	public void PublishAction( T t )
	{
		Program.HandleEvent( new object[] { t } );
	}
}

Querying an instance of a class for its events is as simple as:

C#
MyClass c = new MyClass();

foreach( EventInfo e in c.GetType().GetEvents() )
{
    foreach( object o in e.GetCustomAttributes( true ) )
    {
        if( o is PubEventAttribute )
	{
          ...
        }
    }
}

Creating a delegate from some arbitrary type definition can be done using the Delegate class' static method CreateDelegate. The definition of the particular overload I'm using is:

C#
public static Delegate CreateDelegate(
	Type type,
	Object firstArgument,
	MethodInfo method
)

The reason for using this particular delegate is because my specific application is for an embedded solution running on WinCE, and this is the only overload of CreateDelegate that is supported on WinCE.

The type argument is the type of the delegate being created. This should match the type of the event itself (EventInfo.EventHandlerType). The next argument is the instance that implements the delegate you want to connect to the event, which in our case will be an instance of our ActionPublisher<...> class. The final argument is of the type MethodInfo that describes the public method of the ActionPublisher<...> class that will be called when the event is fired (PublishAction in this case).

The next problem to solve is creating an instance of the ActionPublisher<...> that matches our event type. This requires the ability to instantiate a generic type through reflection. Fortunately, I already wrote an article on this very topic. However, this method requires that I have an array of types describing the generic types of the generic class. Getting this information from the EventInfo class is non-intuitive, but I discovered it to be accessible from EventInfo.EventHandlerType.GetMethod( "Invoke" ).GetParameters() . This call actually returns us an array of ParameterInfo objects describing the generic arguments of the Action event. From this, I can instantiate a matching instance of the ActionPublisher class as follows:

C#
Type publisherType = typeof( ActionPublisher<> ).MakeGenericType
	( new Type[] { actionArgs[ 0 ].ParameterType } );
object publisher = Activator.CreateInstance( publisherType, c, "/Topic" );

Finally, adding the handler to the event can be done using the EventInfo class's AddEventHandler method. This method takes two arguments: the instance of the object implementing the callback and the delegate we just created. The full code is:

C#
ParameterInfo[] actionArgs = eInfo.EventHandlerType.GetMethod
		( "Invoke" ).GetParameters();
if( actionArgs.Length == 1 )
{
	// create the ActionPublisher instance that matches our event
	Type publisherType = typeof( ActionPublisher<> ).MakeGenericType
		( new Type[] { actionArgs[ 0 ].ParameterType } );
	object publisher = Activator.CreateInstance( publisherType, c, "/Topic" );

	// create a delegate to the PublishAction method of the publisher
	MethodInfo publisherInvoke = publisherType.GetMethod( "PublishAction" );
	Delegate d = Delegate.CreateDelegate
			( eInfo.EventHandlerType, publisher, publisherInvoke );

	// wire up the delegate to the event
	eInfo.AddEventHandler( c, d );
	break;
}

The included sample application performs all of these steps for a class that exposes a single event of type Action<int>. This event is decorated with the PubEvent attribute and so gets a dynamically instantiated instance of ActionPublisher<int> added to it. Calling the method DoIt() fires the event. From this, we can see that our dynamically created delegate handler is invoked.

History

  • 26th October, 2010: Initial post

License

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


Written By
Software Developer (Senior)
United States United States
Developer with over twenty years of coding for profit, and innumerable years before that of doing it at a loss.

Comments and Discussions

 
Generalhave 5 Pin
Pranay Rana30-Dec-10 20:20
professionalPranay Rana30-Dec-10 20:20 
GeneralMy vote of 5 Pin
John Brett26-Oct-10 23:12
John Brett26-Oct-10 23:12 
GeneralNice idea Pin
vbfengshui26-Oct-10 10:16
vbfengshui26-Oct-10 10:16 

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.