Introduction
Delegates can be converted easily to another type if you know types of those delegates. But if you do not know it is impossible without really complicated code.
Solution
In example let us consider following console application.
class Program { static void Main(string[] args) { } public static event CustomEvent Event; protected virtual void OnEvent() { Event?.Invoke(this, EventArgs.Empty); } } internal delegate void CustomEvent(object sender, EventArgs args);
Adding handler for CustomeEvent
is pretty easy.
Event += (sender, eventArgs) => { };
Right? What if we want to add as handler already existing delegate? I.e. Action<>
delegate? We can’t just assign one to the another.
Action<object,EventArgs> onEvent = (sender, eventArgs) => { }; Event += onEvent;
Coded like this cause an compile error.
Compiler cannot convert one delegate to another, with different type, implicitly for you. You have to help it a little. Delegates types are really just classes that envelopes method pointer. If we want to convert one delegate of known type to another one with known type, you have to use a constructor.
Action<object,EventArgs> onEvent = (sender, eventArgs) => {}; Event += new CustomEvent(onEvent);
Above code compiles and runs perfectly fine.
There is also another possibility without using constructor. Instead we can assign method with compatible signature to our event.
Action<object,EventArgs> onEvent = (sender, eventArgs) => {}; Event += onEvent.Invoke;
This is the same as assigning some other method to handler like it is done the most usual way.
static void Handler(object sender, EventArgs e){} Event += Handler;
This is really simple. So where is the problem? Right?
Things get more complicated when there is no clear knowledge of a event type in the time of writing code and duding compilation of program. For example things are also moderately easy if you are sure that event is of EventHandler<>
type.
For example if we create two more events based on generic EventHandler<>
type, we also can easily assign handlers to them.
public class Events { public event EventHandler<EventArgs> Event1; public event EventHandler<UnhandledExceptionEventArgs> Event2; } Action<object, EventArgs> handler = (sender, eventArgs) => { }; events.Event1 += handler.Invoke; events.Event2 += handler.Invoke;
Assigning handler
delegate to both events works because UnhandledExceptionEventArgs
is more detailed type then its base type EventArgs
, so there is no need for more complex conversion. It will also works if handler have both arguments of type object because object is even more general then EventArgs
type.
Action<object, object> handler = (sender, eventArgs) => { }; events.Event1 += handler.Invoke; events.Event2 += handler.Invoke;
This gets even more problematic when assigning method signature is not 100% compatible. For example if handler looks like below.
Action<Events, EventArgs> noObjectHandler = (sender, eventArgs) => { };
For that we need more complex conversion.
private static EventHandler<T> ConvertToEventHandler<S,T>(Action<S, T> d) { return (s, e) => { d((S)s, e); }; }
events.Event1 += ConvertToEventHandler<Events, EventArgs>(noObjectHandler); events.Event2 += ConvertToEventHandler<Events, UnhandledExceptionEventArgs>(noObjectHandler);
Still it is only conversion from Action<,>
to EventHandler<>
. If you want to convert to other, custom type of delegate it is no longer possible to do it this way.
For example we cannot create delegate if we do not know its exact type.
public TDelegate CreateDelegate<TDelegate>() where TDelegate : Delegate, new() { return new TDelegate(); }
Creating delegate this way is not permitted in C#. So for example if we would have event like this:
public class Events { public event CustomEvent Event; }
We cannot assign event handler if we have access to event type only by reflection.
var t = typeof(CustomEvent); Activator.CreateInstance(t, handler.Invoke);
This will not work since handler.Invoke
is not valid variable. The only constructor for type of base of Delegate
, that is available through reflection, takes method pointer as a parameter.
To use this constructor we need to call it via Reflection like below.
t.GetConstructors()[0].Invoke(new object[] { handler, handler.Method.MethodHandle.Value });
Or at least we can try to use it, since it will throw an FatalExecutionEngineError
in our application.
It is because we cannot obtain method handle in managed code as it is explained in MethodInfo.MethodHandle documentation.
This property is for access to managed classes from unmanaged code and should not be called from managed code.
How we can use this constructor then?
We need to use this the same way C# compiler use it – from IL.
Let us inspect what IL code is generated by code similar to below:
Event += (sender, eventArgs) => { };
IL code will look similar to this:
IL_0001: ldsfld class DelegatesConversion.CustomEvent DelegatesConversion.Program/'<>c'::'<>9__7_0' IL_0006: dup IL_0007: brtrue.s IL_0020 IL_0009: pop IL_000a: ldsfld class DelegatesConversion.Program/'<>c' DelegatesConversion.Program/'<>c'::'<>9' IL_000f: ldftn instance void DelegatesConversion.Program/'<>c'::'<Main>b__7_0'(object, class [mscorlib]System.EventArgs) IL_0015: newobj instance void DelegatesConversion.CustomEvent::.ctor(object, native int) IL_001a: dup IL_001b: stsfld class DelegatesConversion.CustomEvent DelegatesConversion.Program/'<>c'::'<>9__7_0' IL_0020: call void DelegatesConversion.Program::add_Event(class DelegatesConversion.CustomEvent)
Let us inspect it line by line.
ldsfld
IL code loads field value on top of a stack. dup
IL code duplicates this value on the stack. brtrue.s
instruction checks if value on top of a stack evaluates to boolean
true and transfer control to another address. If it evaluates to false then next address code is executed – in this case pop
code, which removes first value loaded, value of field (the one that was duplicated), because it is null anyway (evaluates to false). In short program checks if compiler generated field with value of (sender, eventArgs) => { };
was already set or it has to be set for the first time. It is done this way because compiler creates instance of a delegate only once and then set to an event the same instance every time you call += code with the same handler. This is compiler code optimization and we are not really interested in it. The next codes are the ones that do all the magic we need. Codes at address 000a and 000f loads parameters for delegate constructor executed by newobj
IL code. First parameter is an instance with method we want to assign to delegate created by constructor. Second parameter is a pointer to this method. Method will be executed by delegate created by constructor. newobj
code executes constructor, creates this new delegate and puts it onto the stack. Next code duplicates this value for the stsfld
code, which stores this value to a field for future use. Last one takes duplicated value of a field to add it to event as a handler by using event add accessor. This way C# compiler adds new handler to an event.
As you can see for converting one type of delegate to another we only need to:
- execute
ldsfld
or similar code to load pointer to an instance with method for delegate - execute
ldftn
for a method pointer - create new delegate by calling constructor via
newobj
code
This way we can create delegate that we can use by just doing +=
with and event.
Event += d;
To create IL code via C# we need a little more complicate code. For example we can use DynamicMethod type for that.
var t = typeof(CustomEvent); var dynamicMethod = new DynamicMethod("converter", typeof(CustomEvent), new[] { typeof(Action<object, EventArgs>) }); var generator = dynamicMethod.GetILGenerator(); generator.Emit(OpCodes.Ldarg_0); generator.Emit(OpCodes.Ldftn, typeof(Action<object, EventArgs>).GetMethod("Invoke")); generator.Emit(OpCodes.Newobj, t.GetConstructors()[0]); generator.Emit(OpCodes.Ret); var converter = (Func<Action<object, EventArgs>, CustomEvent>) dynamicMethod.CreateDelegate(typeof(Func<Action<object, EventArgs>, CustomEvent>));
It is pretty much the same code as above, but instead ldsfld
, ldarg.0
code is used, which loads first argument of a method instead of a field. That way our newly created converter uses parameter as source for conversion.
To use it we only need to call it with parameter of Action<object, EventArgs>
.
Action<object, EventArgs> onEvent = (sender, eventArgs) => { var a = 1; }; var d = converter(onEvent); Event += d; OnEvent();
After running this code we can see that invoking an event cause to invoke onEvent
delegate.
Thing is, from an experience I can tell that using a DynamicMethod
do not work all the time. For some uses on some versions of .NET it throws a critical error. It is safer instead to use AssemblyBuilder
.
var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("dynamicAssembly"), AssemblyBuilderAccess.RunAndCollect); var module = assemblyBuilder.DefineDynamicModule("module"); var typeBuilder = module.DefineType("converter_type"); var methodBuilder = typeBuilder.DefineMethod("converter", MethodAttributes.Static | MethodAttributes.Public | MethodAttributes.Final, CallingConventions.Standard, typeof(CustomEvent), new[] { typeof(Action<object, EventArgs>) }); var generator = methodBuilder.GetILGenerator(); var t = typeof(TDest); generator.Emit(OpCodes.Ldarg_0); generator.Emit(OpCodes.Ldftn, typeof(Action<object, EventArgs>).GetMethod("Invoke")); generator.Emit(OpCodes.Newobj, t.GetConstructors()[0]); generator.Emit(OpCodes.Ret); var type = typeBuilder.CreateType(); var converter = (Func<Action<object, EventArgs>, CustomEvent>)type.GetMethod("converter").CreateDelegate(typeof(Func<Action<object, EventArgs>, CustomEvent>));
This is not that much more complicated. Created converter is used same way as the one created with DynamicMethod
.
Making things better we can create generic method for conversion:
private static Func<TSource, TDest> CreateConverter<TSource, TDest>() { var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("dynamicAssembly"), AssemblyBuilderAccess.RunAndCollect); var module = assemblyBuilder.DefineDynamicModule("module"); var typeBuilder = module.DefineType("converter_type"); var methodBuilder = typeBuilder.DefineMethod("converter", MethodAttributes.Static | MethodAttributes.Public | MethodAttributes.Final, CallingConventions.Standard, typeof(TDest), new[] { typeof(TSource) }); var generator = methodBuilder.GetILGenerator(); var t = typeof(TDest); generator.Emit(OpCodes.Ldarg_0); generator.Emit(OpCodes.Ldftn, typeof(TSource).GetMethod("Invoke")); generator.Emit(OpCodes.Newobj, t.GetConstructors()[0]); generator.Emit(OpCodes.Ret); var type = typeBuilder.CreateType(); var converter = (Func<TSource, TDest>)type.GetMethod("converter").CreateDelegate(typeof(Func<TSource, TDest>)); return converter; }
This way we can create converters in a more flexible way.
var converter = CreateConverter<Action<object,EventArgs>, CustomEvent>();
To use it we just have to call it with appropriate parameter.
Action<object, EventArgs> onEvent = (sender, eventArgs) => { var a = 1; }; var d = converter(onEvent); Event += d; OnEvent();
This code compiles and run perfectly without any problems.
But it is barely usable. After all what is the point of creating some complicated converter since we can just do this:
Event += onEvent.Invoke;
It makes much more sense when we do not have access to type of an event during compile time – when in example type is loaded dynamically or dynamically created during runtime. Then we cannot use C# standard way for adding handler to event. For this case converter
is a must. But then it do not makes sense to require destination type if the only use case is when do not have it. For that we need to change CreateConverter
method.
public static Func<TSource, Delegate> CreateConverter<TSource>(Type destinationType) { var sourceType = typeof(TSource); var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("dynamicAssembly"), AssemblyBuilderAccess.RunAndCollect); var module = assemblyBuilder.DefineDynamicModule("module"); var typeBuilder = module.DefineType("converter_type"); var methodBuilder = typeBuilder.DefineMethod("converter", MethodAttributes.Static | MethodAttributes.Public | MethodAttributes.Final, CallingConventions.Standard, destinationType, new[] { sourceType }); var generator = methodBuilder.GetILGenerator(); generator.Emit(OpCodes.Ldarg_0); generator.Emit(OpCodes.Ldftn, sourceType.GetMethod("Invoke")); generator.Emit(OpCodes.Newobj, destinationType.GetConstructors()[0]); generator.Emit(OpCodes.Ret); var type = typeBuilder.CreateType(); var converter = (Func<TSource, Delegate>)type.GetMethod("converter").CreateDelegate(typeof(Func<TSource, Delegate>)); return converter; }
Now we get generic method with one less type parameter, which is unknown anyway. Below is an example how can we add created delegate as a handler to an event, which is inaccessible during compile time, through a Reflection.
Action<object, EventArgs> onEvent = (sender, eventArgs) => { var a = 1; }; var d = converter(onEvent); var add = typeof(Program).GetEvent("Event", BindingFlags.Static | BindingFlags.NonPublic).GetAddMethod(true); add.Invoke(null, new object[] { d }); OnEvent();
That is it. Working solution for dynamic delegates conversion. But we can make things prettier. I.e. extract method to a separate type.
public static class DelegatesConversion { public static Func<TSource> CreateConverter<TSource>(Type destinationType) { var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("dynamicAssembly"), AssemblyBuilderAccess.RunAndCollect); var module = assemblyBuilder.DefineDynamicModule("module"); var typeBuilder = module.DefineType("converter_type"); var methodBuilder = typeBuilder.DefineMethod("converter", MethodAttributes.Static | MethodAttributes.Public | MethodAttributes.Final, CallingConventions.Standard, destinationType, new[] { typeof(TSource) }); var generator = methodBuilder.GetILGenerator(); generator.Emit(OpCodes.Ldarg_0); generator.Emit(OpCodes.Ldftn, typeof(TSource).GetMethod("Invoke")); generator.Emit(OpCodes.Newobj, destinationType.GetConstructors()[0]); generator.Emit(OpCodes.Ret); var type = typeBuilder.CreateType(); var converter = (Func<TSource, Delegate>)type.GetMethod("converter").CreateDelegate(typeof(Func<TSource, Delegate>)); return converter; } }
Another thing is that sometimes, we do not need to know or do not know source type too (because we are converting two dynamic types of delegates). For this, we can create another overload of CreateConverter
method without type parameters.
public static Func<TSource, Delegate> CreateConverter<TSource>(Type destinationType) { var sourceType = typeof(TSource); return CreateConverterInternal<Func<TSource, Delegate>>(destinationType, sourceType); } public static Func<Delegate, Delegate> CreateConverter(Type sourceType, Type destinationType) { return CreateConverterInternal<Func<Delegate, Delegate>>(destinationType, sourceType); } private static TConverterDelegate CreateConverterInternal<TConverterDelegate> (Type destinationType, Type sourceType) where TConverterDelegate : class { var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("dynamicAssembly"), AssemblyBuilderAccess.RunAndCollect); var module = assemblyBuilder.DefineDynamicModule("module"); var typeBuilder = module.DefineType("converter_type"); var methodBuilder = typeBuilder.DefineMethod("converter", MethodAttributes.Static | MethodAttributes.Public | MethodAttributes.Final, CallingConventions.Standard, destinationType, new[] { sourceType }); var generator = methodBuilder.GetILGenerator(); generator.Emit(OpCodes.Ldarg_0); generator.Emit(OpCodes.Ldftn, sourceType.GetMethod("Invoke")); generator.Emit(OpCodes.Newobj, destinationType.GetConstructors()[0]); generator.Emit(OpCodes.Ret); var type = typeBuilder.CreateType(); var converter = type.GetMethod("converter").CreateDelegate(typeof(TConverterDelegate)) as TConverterDelegate; return converter; }
We extracted code from both overloads to one single, internal method, since they would consist from very similar code anyway. This way is much cleaner.
One last touch to do, would be to validate delegates methods signatures.
private static TConverterDelegate CreateConverterInternal<TConverterDelegate> (Type destinationType, Type sourceType) where TConverterDelegate : class { var sourceParams = GetInvokeMethodParams(sourceType); var destParams = GetInvokeMethodParams(destinationType); if (ValidateSignatures(sourceParams, destParams)) { return null; } var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("dynamicAssembly"), AssemblyBuilderAccess.RunAndCollect); var module = assemblyBuilder.DefineDynamicModule("module"); var typeBuilder = module.DefineType("converter_type"); var methodBuilder = typeBuilder.DefineMethod("converter", MethodAttributes.Static | MethodAttributes.Public | MethodAttributes.Final, CallingConventions.Standard, destinationType, new[] { sourceType }); var generator = methodBuilder.GetILGenerator(); generator.Emit(OpCodes.Ldarg_0); generator.Emit(OpCodes.Ldftn, sourceType.GetMethod("Invoke")); generator.Emit(OpCodes.Newobj, destinationType.GetConstructors()[0]); generator.Emit(OpCodes.Ret); var type = typeBuilder.CreateType(); var converter = type.GetMethod("converter").CreateDelegate(typeof(TConverterDelegate)) as TConverterDelegate; return converter; } private static bool ValidateSignatures(Type[] sourceParams, Type[] destParams) { if (sourceParams.Length == destParams.Length) { for (var i = 0; i < sourceParams.Length; i++) { if (sourceParams[i] != destParams[i]) { return false; } } } return false; } private static Type[] GetInvokeMethodParams(Type delegateType) { return delegateType.GetMethod("Invoke").GetParameters().Select(p => p.ParameterType).ToArray(); }
Delegates are compatible only when their signatures are matching. Since events usually returns void
we only need to check if parameters match. This way converter will not be created for two incompatible delegates.
This is final version of code 🙂 You can download code here or look it up on GitHub.