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

Parameters Flexibility When Late Binding to an Event

, 20 Jun 2011
Rate this:
Please Sign up or sign in to vote.
.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. This article offers a mechanism for allowing this flexibility when late binding to an

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.

Important update

As commented by Richard Deeming, there is a simple solution to the problem and Reflection.Emit is unnecessary (and so is the article). If you need a solution to this issue, please take a look at the end of the Background section. If you would like to have a look at some simple Reflection.Emit, be my guest to the rest of the article. I hope you find it educating. Richard, thanks for your remark.

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.

Important update

As mentioned earlier, Richard Deeming has a very simple solution to this problem (actually, there is no problem). When using the overload of Delegate.CreateDelegate() that takes a MethodInfo, the magic happens just fine:

EventPublisher ep = new EventPublisher();
var eventInfo = ep.GetType().GetEvent("EventPublished");
MethodInfo methodInfo = this.GetType().GetMethod("ATakingMethod", 
                             BindingFlags.Instance | BindingFlags.NonPublic);
Delegate @delegate = Delegate.CreateDelegate(typeof(MyDelegateTakingB), this, methodInfo);
eventInfo.AddEventHandler(ep,@delegate);
ep.Invoke();

If you would like to see some Reflection.Emit, please go ahead and read my (unnecessary) solution.

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 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.
  • June 2011 - Updated to reflect a comment by Richard Deeming with a much simpler solution to the problem.

License

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

About the Author

Asher Barak
Software Developer Ziv systems, Israel
Israel Israel
Starting with Apple IIe BASICA, and working my way through Pascal, Power Builder, Visual basic (and the light office VBA) C, C++, I am now a C# .NET developer and designer (and a big fan of the .NET framework).
 
I am currently leading a major effort writing a new framework for SAP Business One extensions and overseeing the development of four products on the same (yet unfinished) framework

Comments and Discussions

 
GeneralMy vote of 1 PinmemberdvptUml14-Apr-12 23:48 
GeneralMy vote of 1 PinmemberAkram Gassoub22-Jun-11 2:52 
GeneralRe: My vote of 1 PinmemberAsher Barak29-Jun-11 3:25 
GeneralIt's not this hard! PinmemberRichard Deeming8-Jun-11 5:43 
GeneralThis is embarrassing… PinmemberAsher Barak20-Jun-11 3:06 
GeneralWow, it's that hard? PinmemberQwertie7-Jun-11 9:32 

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 | Mobile
Web01 | 2.8.140709.1 | Last Updated 20 Jun 2011
Article Copyright 2011 by Asher Barak
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid