Introduction

.NET allows binding to an event with a method that has a different signature than that of the published delegate, as long as the return type and the parameter types are derived from those in the published delegate.

On some occasions, you might want to create a mechanism that late binds a known method to an unknown event, discovered by Reflection. For instance, think of a method with the signature void Foo(object sendr, EventArgs e) and assume you would like to be provided with an object (maybe a WinForms control) and a name of an event (string) and bind your method to the event. All might seen to be in order as we know that the event has a matching signature where the second parameter is EventArgs or derived from it.

Unfortunately (if you need it), this functionality is not supported. This article offers a mechanism for allowing this flexibility when late binding, while taking a sneak peek into Reflection.Emit.

Background

If you are using a delegate to hold references to methods, as variables, as properties, or as events, or if you just register to other code events, you might be aware that a variable of delegate type can hold and invoke methods that don't have the exact same signature of the delegate.

Suppose you have two classes A and B where B : A and a delegate that takes an instance of B:

class A { } 
class B : A { } 
delegate void MyDelegateTakingB(B b); 

Now, you have a class exposing an event of type MyDelegateTakingB:

class EventPublisher 
{ 
    public event MyDelegateTakingB EventPublished; 
}

Say you have two functions, one that takes B, exactly as the delegate suggests, and another that takes A, which is a supertype of B, which means you can register either one to the published event:

void ATakingMethod(A a) { } 
void BTakingMethod(B b) { } 

This code indeed runs just fine:

EventPublisher ep = new EventPublisher();
ep.EventPublished += BTakingMethod;
ep.EventPublished += ATakingMethod;

However, you might be aware that this is a short version of this:

EventPublisher ep = new EventPublisher();
ep.EventPublished += new MyDelegateTakingB(BTakingMethod);
ep.EventPublished += new MyDelegateTakingB(ATakingMethod);

So, in fact, an event of a given delegate type takes only instances of the proper delegate type. The type relaxation is achieved by the delegate construction's ability to take a method with a less derived parameter.

Just to make the point, assume you have another delegate that matches the ATakingMethod signature like so:

delegate void MyDelegateTakingA(A a);

This will not even build:

ep.EventPublished += new MyDelegateTakingA(ATakingMethod);

WrongDelegateTypeAssignment

Back to our scenario, we have the name of the published event, a target object, and the name of the function we would like to register to the event. We know that the method has a legitimate signature.

Let's start by using the method that takes B and exactly matches the signature of the delegate:

EventPublisher ep = new EventPublisher();
var eventInfo = ep.GetType().GetEvent("EventPublished");
Delegate @delegate = Delegate.CreateDelegate(
          eventInfo.EventHandlerType, this, "BTakingMethod");
eventInfo.AddEventHandler(ep,
@delegate);
ep.Invoke();

This, unsurprisingly, works fine.

Now, let's try and hook up our method ATakingMethod to the same event. Repeating the same pattern:

EventPublisher ep = new EventPublisher();
var eventInfo = ep.GetType().GetEvent("EventPublished");
Delegate @delegate = 
  Delegate.CreateDelegate(eventInfo.EventHandlerType, this, "ATakingMethod");
eventInfo.AddEventHandler(ep,
@delegate);
ep.Invoke();

But...

WrongDelegateTypeAssignment

It appears that, when it comes to late binding, somebody forgot to implement that bit of magic that makes it possible to instantiate a delegate around a method with inherited types.

The Solution

Well, we have a method we want invoked upon the event. We are certain the method would function properly with the parameters provided by the event, but we cannot register it... If only we could create a method that has the "right" signature and have it register to the event and invoke our method... Oh, but we can! Yes, we can! Enter Reflection.Emit.

(If you just desperately need the solution, you can skip to "Using the code".)

Reflection.Emit

There are some ways of creating code dynamically in .NET. You can use CodeDOM if you have the patience for it. For small matters, such as the matter at hand, you might want to consider Reflection.Emit.

It is a well know secret that all .NET languages are compiled into MSIL, which is what the Framework can run. The Reclection.Emit namespace contains all you need to create some IL code on the fly. Unfortunately, I am not an IL expert and you probably don't intend to become one either, but it is well worth getting to know that there is such a tool as Reflection.Emit and I gladly took on the opportunity to put it to a small use and get a taste of it. The following sections describe the way I used Reflection.Emit to create a method of the requested delegate type that invokes the requested method, passes the parameters to it, and returns the return values. I hope it will give you a taste of Reflection.Emit and possibly help you add it to your toolbox for future use.

DynamicMethod

The Reflection.Emit has all sorts of goodies that can be used to create mostly anything, including the rational types and some other interesting things such as module level methods. You might want to check it out. We are going to use the DynamicMethod class that facilitates the creation of methods. The method can then be invoked and attached to existing classes. Using DynamicMethod involves three parts:

  1. declare the method
  2. create the code
  3. compile the code into a delegate

Declare the Method

This is the code for declaring the method:

DynamicMethod dynamicInvoker = new DynamicMethod(
    invokerMethodName,
    createdEventReturnType,
    createdEventTypes,
    invocationTarget.GetType());

Note: this includes most of the things you would expect when defining the signature of a method. If you are creating an instance method (as we are doing), the array containing the types of parameters (here in the variable "createdEventTypes") must start with the type of the object you are about to add this method to (also the last parameter of the DynamicMethod constructor).

Create the Code

To write (or emit) your IL, you need to call this:

ILGenerator ilGenerator = dynamicInvoker.GetILGenerator();

which returns an ILGenerator instance you can use.

Emitting IL orders involves, in most cases, calling ILGenerator.Emit() providing one of the enum values representing all the basic operations that .NET can do. If you have ever had to write some assembly code, this would seem familiar. Invoking a method using IL includes the following:

  1. load the target object to the stack
  2. load all parameters to the stack
  3. call the method
  4. return

Let's load the target and the parameters. Much to our luck, the very same list is the list of arguments provided to our method. All that is left for us is to do is:

// load invocation target and all parameters to the stack:
for (short i = 0; i < invokedMethodParameters.Length + 1; i++)
{
    ilGenerator.Emit(OpCodes.Ldarg_S, i);
}

Note the Length+1 as we load onto the stack the target object as well as the parameters.

Now we need to invoke the method, which is done by simply calling this:

// invoke method:
ilGenerator.EmitCall(OpCodes.Call, invokedMethod, null);

The invokedMethod parameter is of type System.Reflection.MethodInfo.

Now we need to return:

ilGenerator.Emit(OpCodes.Ret);

The fun part is that the call to the method puts the return value (if any) on the stack for us, so calling return now has the return part handled too.

Compiling the Method into a Delegate

Straightforward:

dynamicInvoker.CreateDelegate(createdEventType, invocationTarget);

We provide the type of the delegate we want and the invocation target. That's it.

Word to the Wise

The full code is attached and you can find that quite a bit of attention goes into making sure that the signatures of the invoked method and the target delegate do indeed match. The reason this is all required is that you only get feedback for this late binding at runtime. This means that you might get the parameters or the return type wrong and not know about it until too late. It is even possible to have it right some times and wrong some other times...

Using the Code

Just tell the utility what is the expected type of delegate, what is the method you want to invoke, and where that method is declared, and you get back a delegate containing a method that calls your method:

EventPublisher ep = new EventPublisher();
var eventInfo = ep.GetType().GetEvent("EventPublished");
Delegate @delegate =
    EventLateBindingHelper.DelegateGenerator.CreateEventHandlerMethod(
    eventInfo.EventHandlerType,
    GetType().GetMethod("ATakingMethod", BindingFlags.Instance | BindingFlags.NonPublic),
    this
    );
eventInfo.AddEventHandler(ep,
@delegate);
ep.Invoke();

Points of Interest

You may choose to loosen the checks up a bit here. For instance, you may decide it is OK for the called method to take less parameters than the delegate type. You might want to start mixing and matching parameters by type. I decided to go carefully here. Good luck.

History

  • June 2011 - First release.
推荐.NET配套的通用数据层ORM框架:CYQ.Data 通用数据层框架
新浪微博粉丝精灵,刷粉丝、刷评论、刷转发、企业商家微博营销必备工具"