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);
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...
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:
- declare the method
- create the code
- 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:
- load the target object to the stack
- load all parameters to the stack
- call the method
- 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:
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:
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.
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 full stack developer and development manager. Mostly with MS technologies on the server side and javascript(typescript) frameworks on the client side.