This is third article in series, that explains how to create delegates for public and non-public members of public, non-public or not known in compile-time types. If you are not familiar with previous articles and looking for detailed explanation of how delegates works, how they are created and why, I strongly encourage you to read first two. If you are just looking for a way to create delegates for methods or constructors it is not necessary.
Code with new features and with bug fixes is available on GitHub and as Nuget package.
In first and second article in series, we covered:
- Static
- Properties
- Fields
- Methods
- Instance
- Properties
- Fields
- Indexers
- Methods
- Constructors
Now it is time to cover following members.
- Generic Methods
- Events
Generic methods
Previously we do not covered very important feature of C# regarding methods. If you read header of this section, you already know what I am talking about 🙂 In C# there is possible to write generic methods, that do not have rigid parameters types or rigid return type, because they may vary and depends of type parameters. Most of DelegateFactory class methods are generic. How we can call those methods via delegates? Main problem is that we cannot, or at least not that easy. In reality every version of generic method with different set of type parameters is different method and we need different delegate for that set of parameters.
Static generic methods
Consider following method in TestClass.
public static T StaticGenericMethod<T>() where T : new () { return new T(); } public static T StaticGenericMethod<T>(T param) { return param; }
First is really simple generic method without parameter with return type from type parameter. There are lot of types in C# world that have default parameterless constructor, so we can use it and create very basic generic method that do actually something meaningful 🙂 Second one is even simpler (it just returns the same object of type given by type parameter) but is important to show that is overload of generic method that takes different set of parameters.
Both are static methods so we can use StaticMethod methods, right? Yes we can and it will not work, because we cannot create correct delegate for first StaticGenericMethod example (it will throw error because passed delegate type is not compatible) or will return null if at least one of parameters is of varied type (we cannot search for method overload by its parameters if we do not know them because they differ from type parameters). Solution for those problems are new overloads that will take type parameters (or new parameters with types ) to apply to generic method.
With passing type parameters for delegate is the same problem as with indexers and methods (static and instance) members types. There are really no limits to number of parameter types (aside from reason and 16 limit in Action and Func classes) so we would have to write different StaticMethod and InstanceMethod overload for every number of type parameters. This makes little sense and from experience I can say that mostly methods have one or two type parameters. I probably wrote at some point method with 3 but I do not recall that. This is why (and consistency with other methods of DelegateFactory) support for up 3 type parameters will be enough, I think. For every other case array of types for types parameters will sufficient.
But first we need to obtain method info for generic method with possible overloads in mind. Overload can be any combination of parameters and number of type parameters. For example you can have two overloads with no parameters if they differ with number of type parameters. Or you can have overloads with the same type parameter (or none) but with different set of parameters types. Consider below methods.
public static T StaticGenericMethod<T>() where T : new() { return new T(); } public static T StaticGenericMethod<T>(T param) { return param; } public static T StaticGenericMethod<T>(T param, int i) where T : ITestInterface { return param; } public static T StaticGenericMethod<T>(T param, int i, bool p) where T : struct { return param; } public static T1 StaticGenericMethod<T1, T2>() where T1 : new() { return new T1(); } public static T1 StaticGenericMethod<T1, T2, T3>(int i) where T1 : new() { return new T1(); }
Those are all perfectly valid methods and because of that earlier version of GetStaticMethod method will not work. Why? Reflection do not allow us to easily find generic method overload and Type.GetMethod will throw an exception, if there is more than one overload for the same set of parameters (which is perfectly possible if one or more methods are generic). How to remedy this problem? We can catch AmbiguousMatchException when there is more than one overload and check all methods for the generic one we are looking for. To do that we need to compare two pair of sets: first set of parameters we are looking for and set pf type parameters with correct constraints.
private static MethodInfo GetStaticMethodInfo(Type source, string name, Type[] parametersTypes, Type[] typeParameters = null) { MethodInfo methodInfo = null; try { methodInfo = (source.GetMethod(name, BindingFlags.Static, null, parametersTypes, null) ?? source.GetMethod(name, BindingFlags.Static | BindingFlags.NonPublic, null, parametersTypes, null)) ?? source.GetMethod(name, BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public, null, parametersTypes, null); } catch (AmbiguousMatchException) { //swallow and test generics } //check for generic methods if (typeParameters != null) { var ms = source.GetMethods(BindingFlags.Static) .Concat(source.GetMethods(BindingFlags.NonPublic | BindingFlags.Static)) .Concat(source.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static)); foreach (var m in ms) { if (m.Name == name && m.IsGenericMethod) { var parameters = m.GetParameters(); var genericArguments = m.GetGenericArguments(); var parametersTypesValid = parameters.Length == parametersTypes.Length; parametersTypesValid &= genericArguments.Length == typeParameters.Length; if (!parametersTypesValid) { continue; } for (var index = 0; index < parameters.Length; index++) { var parameterInfo = parameters[index]; var parameterType = parametersTypes[index]; if (parameterInfo.ParameterType != parameterType && parameterInfo.ParameterType.IsGenericParameter && !parameterInfo.ParameterType.CanBeAssignedFrom(parameterType)) { parametersTypesValid = false; break; } } for (var index = 0; index < genericArguments.Length; index++) { var genericArgument = genericArguments[index]; var typeParameter = typeParameters[index]; if (!genericArgument.CanBeAssignedFrom(typeParameter)) { parametersTypesValid = false; break; } } if (parametersTypesValid) { methodInfo = m.MakeGenericMethod(typeParameters); break; } } } } return methodInfo; }
This is much more complicated than before! But do not worry, I will explain everything 🙂
Previous code is inside try statement. First change is one extra parameter with set of type parameters that should be checked against every overload of method generic arguments number and all constraints. If we do look for generic method (typeParameter is not null), we are switching to looking methods one by one. After retrieving all methods of type, with all visibilities, we have to filter them by name. Then we check if method is generic. If it is we have to check number of parameters and type parameters. If those are correct we have to compare them with delegate parameters and type arguments at the same position. Checking for parameters is not that easy as comparing only types because parameters can have type from type parameters, which can have constraints. Because of that we have to check for type of parameter and if it is different (generic parameter have special generic type) we have to check if parameter is generic and if its generic type can represent type of parameter we are looking for. In example int type can be represent by parameter of generic type T with constraint of struct. This may be a little hard to understand without knowledge about detail of IsAssignableFrom method. There is of course Type.IsAssignableFrom method, but strangely it was not working correctly since simple argument without constraints could not be matched with any class. Code of both is the same except for check as below, which was causing false results.
RuntimeType toType = this.UnderlyingSystemType as RuntimeType; if (toType != null) return toType.IsAssignableFrom(c);
I do not know why, but after removing it and creating new method without it works as it should and i.e. TÂ type parameter in method without type parameter constraints:
public static T StaticGenericMethod<T>(T param)
can be assigned from TestClass type, when we check with new implementation of IsAssignableFrom.
public static bool CanBeAssignedFrom(this Type destination, Type source) { if (source == null || destination == null) { return false; } if (destination == source || source.IsSubclassOf(destination)) { return true; } if (destination.IsInterface) { return source.ImplementsInterface(destination); } if (!destination.IsGenericParameter) { return false; } var constraints = destination.GetGenericParameterConstraints(); return constraints.All(t1 => t1.CanBeAssignedFrom(source)); } private static bool ImplementsInterface(this Type source, Type interfaceType) { while (source != null) { var interfaces = source.GetInterfaces(); if (interfaces.Any(i => i == interfaceType || i.ImplementsInterface(interfaceType))) { return true; } source = source.BaseType; } return false; }
Above method have to be used in two situations. First we have to compare given set of parameters of delegate with parameters of a method. This was done previously automatically by searching for method in Type.GetMethod method. But now, since we know, that it is possible to have two generic methods with the same set of parameters, we have to check, against desired types, every generic method overload ourselves. If method do not have generic parameters comparing is easy: just check if two Type type instances are equal. But if method have generic parameters we have to compare type parameter constraints of this parameter. This is done by CanBeAssignedFrom method.
If our set of parameters is correct we have to compare type parameters in similar way. Every provided concrete type have to be check against type parameter of method at the same index and its constraints. It is also done by CanBeAssignedFrom method.
If both sets are correct it means we have our desired generic method overload and it can used to produce method with concrete types by Type.MakeGenericMethod.
Ok. Let’s cover static methods. First case when we know all delegate types, then unknown types (by objects) and after that we will do the same for instance methods.
First new overloads for generics are very easy to implement. Since we already have working GetStaticMethodInfo method, that not only searches for method info, but also creates non-generic method with specified type parameters, we can use returned object to create delegate as for non-generic delegates. The only difference are extra type parameters for delegate method… well type parameters. It will look much more clear in code.
public static TDelegate StaticMethod<TSource, TDelegate, TParam1>(string name) where TDelegate : class { return typeof(TSource).StaticMethod<TDelegate>(name, typeof(TParam1)); } public static TDelegate StaticMethod<TSource, TDelegate, TParam1, TParam2>(string name) where TDelegate : class { return typeof(TSource).StaticMethod<TDelegate>(name, typeof(TParam1), typeof(TParam2)); } public static TDelegate StaticMethod<TSource, TDelegate, TParam1, TParam2, TParam3>(string name) where TDelegate : class { return typeof(TSource).StaticMethod<TDelegate>(name, typeof(TParam1), typeof(TParam2), typeof(TParam3)); } public static TDelegate StaticMethod<TSource, TDelegate>(string name, params Type[] typeParameters) where TDelegate : class { return typeof(TSource).StaticMethod<TDelegate>(name, typeParameters); } public static TDelegate StaticMethod<TDelegate>(this Type source, string name, params Type[] typeParameters) where TDelegate : class { var paramsTypes = GetFuncDelegateArguments<TDelegate>(); var methodInfo = GetStaticMethodInfo(source, name, paramsTypes, typeParameters); return methodInfo?.CreateDelegate(typeof(TDelegate)) as TDelegate; }
First three are just overloads with use of 1, 2 or 3 type parameters for generic method with the same number of type parameters. Next method allow to create delegate with more type parameters. Most important is last method that do actual work of creating delegate.
To use above methods for generic methods delegate we have to call them in following way (first examplary call and then destination generic method that will be executed via delegate).
var g1 = DelegateFactory.StaticMethod<TestClass, Func<TestClass, TestClass>, TestClass>("StaticGenericMethod"); public static T StaticGenericMethod<T>(T param) var g2 = DelegateFactory.StaticMethod<TestClass, Func<TestClass, int, TestClass>, TestClass>("StaticGenericMethod"); public static T StaticGenericMethod<T>(T param, int i) where T : ITestInterface var g3 = DelegateFactory.StaticMethod<TestClass, Func<TestStruct, int, bool, TestStruct>, TestStruct>("StaticGenericMethod"); public static T StaticGenericMethod<T>(T param, int i, bool p) where T : struct var g4 = DelegateFactory.StaticMethod<TestClass, Func<TestClass>, TestClass>("StaticGenericMethod"); public static T StaticGenericMethod<T>() where T : new() var g5 = DelegateFactory.StaticMethod<TestClass, Func<TestClass>, TestClass, TestStruct>("StaticGenericMethod"); public static T1 StaticGenericMethod<T1, T2>() where T1 : new() var g6 = DelegateFactory.StaticMethod<TestClass, Func<int, TestClass>, TestClass, TestStruct, int>("StaticGenericMethod"); public static T1 StaticGenericMethod<T1, T2, T3>(int i) where T1 : new() var g7 = DelegateFactory.StaticMethod<TestClass, Func<int, TestClass>>("StaticGenericMethod", typeof(TestClass), typeof(TestStruct), typeof(int)); public static T1 StaticGenericMethod<T1, T2, T3>(int i) where T1 : new()
Looks very easy when explained this way 🙂
We already have StaticMethod overload as extension method for Type instance. We can also write three new overloads for cases when we want to create delegates for 1, 2 or 3 type parameters and we do not know or do not want to use source type.
public static TDelegate StaticMethod<TDelegate, TParam1>(this Type source, string name) where TDelegate : class { return source.StaticMethod<TDelegate>(name, typeof(TParam1)); } public static TDelegate StaticMethod<TDelegate, TParam1, TParam2>(this Type source, string name) where TDelegate : class { return source.StaticMethod<TDelegate>(name, typeof(TParam1), typeof(TParam2)); } public static TDelegate StaticMethod<TDelegate, TParam1, TParam2, TParam3>(this Type source, string name) where TDelegate : class { return source.StaticMethod<TDelegate>(name, typeof(TParam1), typeof(TParam2), typeof(TParam3)); }
They are used in the same way as source type passed as first type parameter.
Ok. Now we can jump to case when we do not know any of types (or do not want to use those). Produced delegate should use object type for parameters and for return value. It was done in static methods, instance methods, properties and indexers. Main problem with this function is that we cannot do it as overload of StaticMethod because one of previous overloads already allows params parameters. Any other overload with array of types would be compile error. Also having in mind, that for method without passed delegate type we have to write two methods, for void return type and for non-void types, we will create methods StaticGenericMethod and StaticGenericMethodVoid. Luckily both can be handled by the same code as StaticMethod and StaticMethodVoid with just simple change.
public static Func<object[], object> StaticGenericMethod(this Type source, string name, Type[] paramsTypes, Type[] typeParams) { return StaticMethod<Func<object[], object>>(source, name, typeParams, paramsTypes); } public static Action<object[]> StaticGenericMethodVoid(this Type source, string name, Type[] paramsTypes, Type[] typeParams) { return StaticMethod<Action<object[]>>(source, name, typeParams, paramsTypes); }
Changed implementation of previous methods will look like this.
public static Func<object[], object> StaticMethod(this Type source, string name, params Type[] paramsTypes) { return StaticMethod<Func<object[], object>>(source, name, null, paramsTypes); } public static TDelegate StaticMethod<TDelegate>(this Type source, string name, Type[] typeParams, Type[] paramsTypes) where TDelegate : class { var methodInfo = GetStaticMethodInfo(source, name, paramsTypes, typeParams); if (methodInfo == null) { return null; } var argsArray = Expression.Parameter(typeof(object[])); var paramsExpression = new Expression[paramsTypes.Length]; for (var i = 0; i < paramsTypes.Length; i++) { var argType = paramsTypes[i]; paramsExpression[i] = Expression.Convert(Expression.ArrayIndex(argsArray, Expression.Constant(i)), argType); } Expression returnExpression = Expression.Call(methodInfo, paramsExpression); if (methodInfo.ReturnType != typeof(void) && !methodInfo.ReturnType.IsClass) { returnExpression = Expression.Convert(returnExpression, typeof(object)); } return Expression.Lambda(returnExpression, argsArray).Compile() as TDelegate; } public static Action<object[]> StaticMethodVoid(this Type source, string name, params Type[] paramsTypes) { return StaticMethod<Action<object[]>>(source, name, null, paramsTypes); }
Now with everything ready for static generic methods, we can start tests. Below calls will create delegates for different overloads of StaticGenericMethod (lines in every block) and using different overloads of StaticMethod or StaticGenericMethod (three blocks).
var g1 = DelegateFactory.StaticMethod<TestClass, Func<TestClass, TestClass>, TestClass>("StaticGenericMethod"); var g2 = DelegateFactory.StaticMethod<TestClass, Func<TestClass, int, TestClass>, TestClass>("StaticGenericMethod"); var g3 = DelegateFactory.StaticMethod<TestClass, Func<TestStruct, int, bool, TestStruct>, TestStruct>("StaticGenericMethod"); var g4 = DelegateFactory.StaticMethod<TestClass, Func, TestClass>("StaticGenericMethod"); var g5 = DelegateFactory.StaticMethod<TestClass, Func, TestClass, TestStruct>("StaticGenericMethod"); var g6 = DelegateFactory.StaticMethod<TestClass, Func<int, TestClass>, TestClass, TestStruct, int>("StaticGenericMethod"); var g7 = DelegateFactory.StaticMethod<TestClass, Func<int, TestClass>>("StaticGenericMethod", typeof(TestClass), typeof(TestStruct), typeof(int)); var g8 = Type.StaticMethod<Func<TestClass, TestClass>, TestClass>("StaticGenericMethod"); var g9 = Type.StaticMethod<Func<TestClass, int, TestClass>, TestClass>("StaticGenericMethod"); var g10 = Type.StaticMethod<Func<TestStruct, int, bool, TestStruct>, TestStruct>("StaticGenericMethod"); var g11 = Type.StaticMethod<Func, TestClass>("StaticGenericMethod"); var g12 = Type.StaticMethod<Func, TestClass, TestStruct>("StaticGenericMethod"); var g13 = Type.StaticMethod<Func<int, TestClass>, TestClass, TestStruct, int>("StaticGenericMethod"); var g14 = Type.StaticMethod<Func<int, TestClass>>("StaticGenericMethod", typeof(TestClass), typeof(TestStruct), typeof(int)); var g15 = Type.StaticGenericMethod("StaticGenericMethod", new[] { Type }, new[] { Type }); var g16 = Type.StaticGenericMethod("StaticGenericMethod", new[] { Type, typeof(int) }, new[] { Type }); var g17 = Type.StaticGenericMethod("StaticGenericMethod", new[] { typeof(TestStruct), typeof(int), typeof(bool) }, new[] { typeof(TestStruct) }); var g18 = Type.StaticGenericMethod("StaticGenericMethod", Type.EmptyTypes, new[] { Type }); var g19 = Type.StaticGenericMethod("StaticGenericMethod", Type.EmptyTypes, new[] { Type, typeof(TestStruct) }); var g20 = Type.StaticGenericMethod("StaticGenericMethod", new[] { typeof(int) }, new[] { Type, typeof(TestStruct), typeof(int) }); var g21 = Type.StaticGenericMethodVoid("StaticGenericMethodVoid", new[] { Type }, new[] { Type });
Created delegates are used in following way.
var t = g1(TestInstance); var t2 = g2(TestInstance, 0); var t3 = g3(new TestStruct(), 0, false); var t4 = g4(); var t5 = g5(); var t6 = g6(0); var t7 = g7(0); var t8 = g8(TestInstance); var t9 = g9(TestInstance, 0); var t10 = g10(new TestStruct(), 0, false); var t11 = g11(); var t12 = g12(); var t13 = g13(0); var t14 = g14(0); var t15 = g15(new object[] { TestInstance }); var t16 = g16(new object[] { TestInstance, 0 }); var t17 = g17(new object[] { new TestStruct(), 0, false }); var t18 = g18(new object[] { }); var t19 = g19(new object[] { }); var t20 = g20(new object[] { 0 }); g21(new object[] { TestInstance }); var t21 = TestClass.StaticGenericMethodVoidParameter;
We can also test performance of delegates for generic method against direct call and reflection.
_stopWatch = new Stopwatch(); _stopWatch.Start(); for (var i = 0; i < _delay; i++) { var test = TestClass.StaticGenericMethod(TestInstance); } _stopWatch.Stop(); Console.WriteLine("Static generic method directly: {0}", _stopWatch.ElapsedMilliseconds); _stopWatch = new Stopwatch(); _stopWatch.Start(); for (var i = 0; i < _delay; i++) { var test = g1(TestInstance); } _stopWatch.Stop(); Console.WriteLine("Static generic method proxy: {0}", _stopWatch.ElapsedMilliseconds); var methodInfos = Type.GetMethods(BindingFlags.Static | BindingFlags.Public).Where(m => m.Name == "StaticGenericMethod" && m.IsGenericMethod && m.GetParameters().Length == 1 && m.GetGenericArguments().Length==1); var methodInfo = methodInfos.Single(); methodInfo = methodInfo.MakeGenericMethod(Type); _stopWatch = new Stopwatch(); _stopWatch.Start(); for (var i = 0; i < _delay; i++) { var test = methodInfo.Invoke(null, new object[] { TestInstance }); } _stopWatch.Stop(); Console.WriteLine("Static generic method via reflection: {0}", _stopWatch.ElapsedMilliseconds);
Above code will spawn output similar lines in console.
Static generic method directly: 1160 Static generic method proxy: 1270 Static generic method via reflection: 21591
As you can see performance is consistent with other delegates for different type of members.
Instance generic methods
We can now do the same for instance methods. But first, we have to change GetMethodInfo in the same way as GetStaticMethodInfo.
private static MethodInfo GetMethodInfo(Type source, string name, Type[] parametersTypes, Type[] typeParameters = null) { MethodInfo methodInfo = null; try { methodInfo = (source.GetMethod(name, BindingFlags.Instance | BindingFlags.Public, null, parametersTypes, null) ?? source.GetMethod(name, BindingFlags.Instance | BindingFlags.NonPublic, null, parametersTypes, null)) ?? source.GetMethod(name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public, null, parametersTypes, null); } catch (AmbiguousMatchException) { //swallow and test generics } //check for generic methods if (typeParameters != null) { var ms = source.GetMethods(BindingFlags.Instance | BindingFlags.Public) .Concat(source.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance)) .Concat(source.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)); foreach (var m in ms) { if (m.Name == name && m.IsGenericMethod) { var parameters = m.GetParameters(); var genericArguments = m.GetGenericArguments(); var parametersTypesValid = parameters.Length == parametersTypes.Length; parametersTypesValid &= genericArguments.Length == typeParameters.Length; if (!parametersTypesValid) { continue; } for (var index = 0; index < parameters.Length; index++) { var parameterInfo = parameters[index]; var parameterType = parametersTypes[index]; if (parameterInfo.ParameterType != parameterType && parameterInfo.ParameterType.IsGenericParameter && !parameterInfo.ParameterType.CanBeAssignedFrom(parameterType)) { parametersTypesValid = false; break; } } for (var index = 0; index < genericArguments.Length; index++) { var genericArgument = genericArguments[index]; var typeParameter = typeParameters[index]; if (!genericArgument.CanBeAssignedFrom(typeParameter)) { parametersTypesValid = false; break; } } if (parametersTypesValid) { methodInfo = m.MakeGenericMethod(typeParameters); break; } } } } return methodInfo; }
As you can see code is almost the same. Difference is in binding flags – Instance instead of Static value is passed.
Now we can add new overloads. We should start with overloads with type parameters only, just like with static methods. They will look like this.
public static TDelegate InstanceMethod<TDelegate, TParam1>(string name) where TDelegate : class { return InstanceMethod<TDelegate>(name, typeof(TParam1)); } public static TDelegate InstanceMethod<TDelegate, TParam1, TParam2>(string name) where TDelegate : class { return InstanceMethod<TDelegate>(name, typeof(TParam1), typeof(TParam2)); } public static TDelegate InstanceMethod<TDelegate, TParam1, TParam2, TParam3>(string name) where TDelegate : class { return InstanceMethod<TDelegate>(name, typeof(TParam1), typeof(TParam2), typeof(TParam3)); } public static TDelegate InstanceMethod<TDelegate>(string name, params Type[] typeParameters) where TDelegate : class { var paramsTypes = GetFuncDelegateArguments<TDelegate>(); var source = paramsTypes.First(); paramsTypes = paramsTypes.Skip(1).ToArray(); var methodInfo = GetMethodInfo(source, name, paramsTypes, typeParameters); return methodInfo?.CreateDelegate(typeof(TDelegate)) as TDelegate; }
Code is very similar to the one without support for generics and new overloads are also easy to understand.
The same case is with extension methods overloads. The only difference is extra parameters and type parameters, that are passed to GetMethodInfo method.
public static TDelegate InstanceMethod<TDelegate, TParam1>(this Type source, string name) where TDelegate : class { return source.InstanceMethod<TDelegate>(name, new[] { typeof(TParam1) }); } public static TDelegate InstanceMethod<TDelegate, TParam1, TParam2>(this Type source, string name) where TDelegate : class { return source.InstanceMethod<TDelegate>(name, new[] { typeof(TParam1), typeof(TParam2) }); } public static TDelegate InstanceMethod<TDelegate, TParam1, TParam2, TParam3>(this Type source, string name) where TDelegate : class { return source.InstanceMethod<TDelegate>(name, new[] { typeof(TParam1), typeof(TParam2), typeof(TParam3) }); } public static TDelegate InstanceMethod<TDelegate>(this Type source, string name, Type[] typeParams = null) where TDelegate : class { var delegateParams = GetFuncDelegateArguments<TDelegate>(); var instanceParam = delegateParams[0]; delegateParams = delegateParams.Skip(1).ToArray(); var methodInfo = GetMethodInfo(source, name, delegateParams, typeParams); if (methodInfo == null) { return null; } Delegate deleg; if (instanceParam == source) { deleg = methodInfo.CreateDelegate(typeof(TDelegate)); } else { var sourceParameter = Expression.Parameter(typeof(object)); var expressions = delegateParams.Select(Expression.Parameter).ToArray(); Expression returnExpression = Expression.Call(Expression.Convert(sourceParameter, source), methodInfo, expressions.Cast<Expression>()); if (methodInfo.ReturnType != typeof(void) && !methodInfo.ReturnType.IsClass) { returnExpression = Expression.Convert(returnExpression, typeof(object)); } var lamdaParams = new[] { sourceParameter }.Concat(expressions); deleg = Expression.Lambda(returnExpression, lamdaParams).Compile(); } return deleg as TDelegate; }
Last two, more general methods, that only takes object parameters and returns objects, are also very similar and changed in the same way as with static generics.
public static Func<object, object[], object> InstanceGenericMethod(this Type source, string name, Type[] paramsTypes, Type[] typeParams) { return InstanceGenericMethod<Func<object, object[], object>>(source, name, typeParams, paramsTypes); } public static Action<object, object[]> InstanceGenericMethodVoid(this Type source, string name, Type[] paramsTypes, Type[] typeParams) { return InstanceGenericMethod<Action<object, object[]>>(source, name, typeParams, paramsTypes); } public static Func<object, object[], object> InstanceMethod(this Type source, string name, params Type[] paramsTypes) { return InstanceGenericMethod<Func<object, object[], object>>(source, name, null, paramsTypes); } public static Action<object, object[]> InstanceMethodVoid(this Type source, string name, params Type[] paramsTypes) { return InstanceGenericMethod<Action<object, object[]>>(source, name, null, paramsTypes); } public static TDelegate InstanceGenericMethod<TDelegate>(this Type source, string name, Type[] typeParams, Type[] paramsTypes) where TDelegate : class { var methodInfo = GetMethodInfo(source, name, paramsTypes, typeParams); if (methodInfo == null) { return null; } var argsArray = Expression.Parameter(typeof(object[])); var sourceParameter = Expression.Parameter(typeof(object)); var paramsExpression = new Expression[paramsTypes.Length]; for (var i = 0; i < paramsTypes.Length; i++) { var argType = paramsTypes[i]; paramsExpression[i] = Expression.Convert(Expression.ArrayIndex(argsArray, Expression.Constant(i)), argType); } Expression returnExpression = Expression.Call(Expression.Convert(sourceParameter, source), methodInfo, paramsExpression); if (methodInfo.ReturnType != typeof(void) && !methodInfo.ReturnType.IsClass) { returnExpression = Expression.Convert(returnExpression, typeof(object)); } return Expression.Lambda(returnExpression, sourceParameter, argsArray).Compile() as TDelegate; }
That is all we need to support instance generic methods. Now we can write some test methods.
public T GenericMethod<T>(T s) { return s; } public object InstanceGenericMethodVoidParameter; public void GenericMethodVoid<T>(T s) { InstanceGenericMethodVoidParameter = s; }
To create delegates for above, we call newly created overloads the same way as for static equivalents. The only difference is one extra parameter for delegates – instance.
var ig1 = DelegateFactory.InstanceMethod<Func<TestClass, TestClass, TestClass>, TestClass>("GenericMethod"); var ig2 = Type.InstanceMethod<Func<TestClass, TestClass, TestClass>, TestClass>("GenericMethod"); var ig3 = Type.InstanceMethod<Func<object, TestClass, TestClass>, TestClass>("GenericMethod"); var ig4 = Type.InstanceGenericMethod("GenericMethod", new[] { Type }, new[] { Type }); var ig5 = Type.InstanceGenericMethodVoid("GenericMethodVoid", new[] { Type }, new[] { Type });
Delegates are called the same way as for any other instance methods delegates.
var it1 = ig1(TestInstance, TestInstance); var it2 = ig2(TestInstance, TestInstance); var it3 = ig3(TestInstance, TestInstance); var it4 = ig4(TestInstance, new object[] { TestInstance }); ig5(TestInstance, new object[] { TestInstance }); var it5 = TestInstance.InstanceGenericMethodVoidParameter;
That is all we need for full support of generic methods.
As you can see main difference and disadvantage of delegates for generics is that if we want full types information we need to create separate delegate for every collection of types we need. Alternatively it is possible to create delegate for generic method with more general type. In example if generic method allows some type parameter to be only of IDisposable interface it is possible to search for method and create delegate from this method with this interface. Below code is perfectly valid in compile-time and runtime.
var g22 = Type.StaticGenericMethodVoid("StaticGenericMethodVoid", new[] { typeof(object) }, new[] { typeof(object) }); g22(new object[] { "" }); var t22 = TestClass.StaticGenericMethodVoidParameter; g22(new object[] { TestInstance }); var t23 = TestClass.StaticGenericMethodVoidParameter;
Now we discussed pretty much everything about methods. It is time for the last type of member – events.
Events
Static events will not be part of this article. As I explained in first article static events are something that I would never use. If you are using them you can write me in the comments and I will update article with delegates for them 🙂
Like with properties accessor, for events we also have two accessors – add and remove. For both we will add method, with few overloads. We should create few test event first.
public event EventHandler PublicEvent; private event EventHandler InternalEventBackend; internal event EventHandler InternalEvent { add { InternalEventBackend += value; } remove { InternalEventBackend -= value; } } protected event EventHandler ProtectedEvent; private event EventHandler PrivateEvent; public void InvokeInternalEvent() { InternalEventBackend?.Invoke(this, new InternalEventArgs()); } public void InvokePrivateEvent() { PrivateEvent?.Invoke(this, new PrivateEventArgs()); } public void InvokeProtectedEvent() { ProtectedEvent?.Invoke(this, new ProtectedEventArgs()); } public void InvokePublicEvent() { PublicEvent?.Invoke(this, new PublicEventArgs()); }
Four events with different visibility. Internal event have custom accessors just to test if that kind of event also will work with delegates. Public methods for invoking events are added just for pure testing (we need a way to raise event from test code outside of TestClass type).
Since to raise an event we need to add handler first we will discuss add accessors first.
Event add accessors
New methods in DelegateFactory will be called EventAdd. But first we need to acquire EventInfo from reflection.
private static EventInfo GetEventInfo(string eventName, Type sourceType) { return (sourceType.GetEvent(eventName) ?? sourceType.GetEvent(eventName, BindingFlags.NonPublic)) ?? sourceType.GetEvent(eventName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance); }
Nothing fancy here. It is the same code like for any other member. Just instead of property or method it looks for event.
If we know type of object with event we want to access, know type of delegate and know its event arguments type, things are really simple. We just need to pass valid delegate type in order to create delegate from accessor method.
public static Action<TSource, EventHandler<TEventArgs>> EventAdd<TSource, TEventArgs>(string eventName) { var sourceType = typeof(TSource); var eventInfo = GetEventInfo(eventName, sourceType); return (Action<TSource, EventHandler<TEventArgs>>) eventInfo?.AddMethod.CreateDelegate(typeof(Action<TSource, EventHandler<TEventArgs>>)); }
It works the same as creating delegates for get accessor of properties. For example for TestClass.PublicEvent event it will create delegate, that could be represented by below lambda.
Action<TestClass, EventHandler<TestClass.PublicEventArgs>> d = (i, h) => i.PublicEvent += h;
Consider now case with unknown source type or when this type just do not matter. After all event handlers always have first parameter of type object so it is not really necessary from user point of view. The only problem in this case is necessity to cast instance parameter from object to correct type with event. As before, we need expressions for that.
public static Action<object, EventHandler<TEventArgs>> EventAdd<TEventArgs>( this Type source, string eventName) { var eventInfo = GetEventInfo(eventName, source); if (eventInfo != null) { var instanceParameter = Expression.Parameter(typeof(object)); var delegateTypeParameter = Expression.Parameter(typeof(EventHandler<TEventArgs>)); var lambda = Expression.Lambda(Expression.Call(Expression.Convert(instanceParameter, source), eventInfo.AddMethod, delegateTypeParameter), instanceParameter, delegateTypeParameter); return (Action<object, EventHandler<TEventArgs>>)lambda.Compile(); } return null; }
Created lambda expression just calls event info AddMethod on converted to source type instance parameter, with handler from second parameter. And as usually this is much easier to understand in form of a lambda 🙂
Action<object, EventHandler<TestClass.PublicEventArgs>> d = (i, h) => ((TestClass)i).PublicEvent += h;
How about a case, when we know type of class and we do not know type of event arguments, i.e. when event is private? The only way to create delegate is via expressions (similar problem to situation when we do not know parameters of method). Unfortunately this is a little more complicated since every event handler takes object and some type that derives from EventArgs (or not since it is more a convention than mandatory). Why? Consider below code for binding handler to event.
TestInstance.PublicEvent += (sender, args) => { };
Nothing complicated right? This code just adds lambda function to event. But how exactly? Look at code that is equivalent of above.
EventHandler<TestClass.PublicEventArgs> ha = (sender, args) => { }; TestInstance.PublicEvent += ha.Invoke;
First new object of EventHandler<> type is created with our lambda function as target. After that the same lambda in Invoke method is passed as handler to event. Obviously we need to create object of EventHandler type from Action<object,EventArgs> type. How to do that? Most obvious thing to try is to use constructor of EventHandler<> type.
EventHandler<TestClass.PublicEventArgs> ha = new EventHandler<TestClass.PublicEventArgs>((sender, args) => { });
This works flawlessly. Since we already saw how to use constructors from reflection and expressions it should be easy to create expression that calls constructor retrieved from reflection. Well… not quite.
As you can see this type have only one constructor and it involves pointer to some object. Which one? What is happening really, when you create new object of EventHandler type? We can check in CIL code compiled from C#.
IL_0011: ldsfld class Delegates.TestEventHandler/'<>c' Delegates.TestEventHandler/'<>c'::'<>9' IL_0016: ldftn instance void Delegates.TestEventHandler/'<>c'::'<.ctor>b__0_0'(object, class Delegates.TestClass/PublicEventArgs) IL_001c: newobj instance void class [mscorlib]System.EventHandler`1<class Delegates.TestClass/PublicEventArgs>::.ctor(object, native int)
Magic happens in ldftn instruction which means: ‘Push a pointer to a method referenced by method, on the stack.’ according to this page. This means that pointer to method (sender, args) => { } is loaded and with this pointer new EventHandler is created. Because of that we cannot use this constructor because we cannot obtain pointer to a managed method from C#.
What can we do, then? The simplest solution is to create new method that do this for us. Simple call to new EventHandler() will suffice. We then can use that method in expressions.
Again there is a small problem. As you already know you can’t remove event handler if you do not have this handler instance. And you cannot create event instance because you do not know its type. The only thing we can do is to save somewhere relation between user handler (i.e Action<objec, object>) and real EventHandler<EventArgs> handler. Of course it is possible to write EventAdd method, that creates delegate, that returns event handler instance. But it would be somehow unintuitive for end user who expects to work with delegates the same way (or at least as close as possible) as with events. Because of that best idea is to store relations in dictionary in DelegateFactory – it will be transparent for user.
private static readonly Dictionary<WeakReference<object>, WeakReference<object>> EventsProxies = new Dictionary<WeakReference<object>, WeakReference<object>>();
Now we can implement EventHandlerFactory method.
public static EventHandler<TEventArgs> EventHandlerFactory<TEventArgs, TSource>( object handler, bool isRemove) where TEventArgs : class { EventHandler<TEventArgs> newEventHandler; var haveKey = false; var kv = EventsProxies.FirstOrDefault(k => { object keyTarget; k.Key.TryGetTarget(out keyTarget); if (Equals(keyTarget, handler)) { haveKey = true; return true; } return false; }); if (haveKey) { object fromCache; EventsProxies[kv.Key].TryGetTarget(out fromCache); newEventHandler = (EventHandler<TEventArgs>)fromCache; if (isRemove) { EventsProxies.Remove(kv.Key); return newEventHandler; } } if (!isRemove) { var action = handler as Action<TSource, object>; if (action != null) { newEventHandler = (s, a) => action((TSource)s, a); } else { newEventHandler = new EventHandler<TEventArgs>((Action<object, object>)handler); } EventsProxies[new WeakReference<object>(handler)] = new WeakReference<object>(newEventHandler); return newEventHandler; } return null; }
Idea is really simple. First we are looking for already generated proxy in EventProxies dictionary. If key with reference stored in WeakReference is equal to provided handler we store key-value pair structure and set haveKey variable. We have to use separate bool variable because KeyValuePair type is structure and we cannot perform null check on structure. If key was found we retrieve event handler from target of WeakReference. isRemove parameter is used to differentiate between retrieving/creating event handler for add accessor and remove accessor. For removing event handler we do not need to create it first (because we could not perform removing of handler that way anyway) and if we retrieve saved handler we can remove it from cache at the same time. For add accessor if event handler is not found in cache we can create new one. If provided action handler have first parameter of type different than object we need another lambda proxy with object parameter. It is because events in .NET always have first parameter of object and EventHandler do not accepts different signature. Otherwise we can safely use action directly, passing it to EventHandler constructor. This is why handler parameter have type of object – we need a way to pass different signatures of methods, depending of EventAdd/EventRemove overload. After saving newly created event handler we are done.
Since we are working with incompatible signatures of event handlers and we are writing such complicated factory method in order to create correct ones, we need just one more trick to use it from expressions (and there is no other way). New field in DelegateFactory class will be enough.
private static readonly MethodInfo EventHandlerFactoryMethodInfo = typeof(DelegateFactory).GetMethod("EventHandlerFactory");
This field will be used inside of expressions in similar way as any other MethodInfo object, but after providing type parameters (because it is generic method).
Now we can write new implementation of EventAdd with provided handler with first parameter of source type.
public static Action<TSource, Action<TSource, object>> EventAdd<TSource>(string eventName) { var source = typeof(TSource); var eventInfo = GetEventInfo(eventName, source); if (eventInfo != null) { var eventArgsType = eventInfo.EventHandlerType.GetGenericArguments()[0]; var delegateType = typeof(Action<,>).MakeGenericType(typeof(TSource), typeof(object)); var instanceParameter = Expression.Parameter(source); var delegateTypeParameter = Expression.Parameter(delegateType); var methodCallExpression = Expression.Call(EventHandlerFactoryMethodInfo.MakeGenericMethod(eventArgsType, source), delegateTypeParameter, Expression.Constant(false)); var lambda = Expression.Lambda( Expression.Call(instanceParameter, eventInfo.AddMethod, methodCallExpression), instanceParameter, delegateTypeParameter); return lambda.Compile() as Action<TSource, Action<TSource, object>>; } return null; }
This overload is very similar to previous one, except instead of directly passing delegate to add accessor we instead are passing result of EventHandlerFactory method. To do that we need to obtain event arguments type from eventInfo.EventHandlerType. Both with type of source we create non generic version of method and call it with incompatible handler as parameter and with constant of Boolean.False. This way we will obtain correct handler from cache or from EventHandler constructor. And of course delegateTypeParameter is of object type (since this is type of first parameter in EventHandlerFactory method) instead of EventHandler<>. Resulted delegate will be similar to below lambda.
Action<TestClass, object> d = (i, h) => i.PublicEvent += DelegateFactory.EventHandlerFactory<TestClass.PublicEventArgs,TestClass>(h, false);
Now we can implement last one overload of EventAdd method and much more usable since do not require to know any of types (source type or event arguments type). Working implementation looks like this.
public static Action<object, Action<object, object>> EventAdd(this Type source, string eventName) { var eventInfo = GetEventInfo(eventName, source); if (eventInfo != null) { var eventArgsType = eventInfo.EventHandlerType.GetGenericArguments()[0]; var instanceParameter = Expression.Parameter(typeof(object)); var delegateTypeParameter = Expression.Parameter(typeof(object)); var methodCallExpression = Expression.Call(EventHandlerFactoryMethodInfo.MakeGenericMethod(eventArgsType, source), delegateTypeParameter, Expression.Constant(false)); var lambda = Expression.Lambda(Expression.Call(Expression.Convert(instanceParameter, source), eventInfo.AddMethod, methodCallExpression), instanceParameter, delegateTypeParameter); return lambda.Compile() as Action<object, Action<object, object>>; } return null; }
Thing is the same code as before. The only difference is resulting type of delegate. It is because we wrote EventHandlerFactory to support both. Because of that we can create third method with implementation and rewrite previous two.
private static TDelegate EventAddImpl<TDelegate>(this Type source, string eventName) where TDelegate : class { var eventInfo = GetEventInfo(eventName, source); if (eventInfo != null) { var eventArgsType = eventInfo.EventHandlerType.GetGenericArguments()[0]; var instanceParameter = Expression.Parameter(typeof(object)); var delegateTypeParameter = Expression.Parameter(typeof(object)); var methodCallExpression = Expression.Call(EventHandlerFactoryMethodInfo.MakeGenericMethod(eventArgsType, source), delegateTypeParameter, Expression.Constant(false)); var lambda = Expression.Lambda(Expression.Call(Expression.Convert(instanceParameter, source), eventInfo.AddMethod, methodCallExpression), instanceParameter, delegateTypeParameter); return lambda.Compile() as TDelegate; } return null; } public static Action<TSource, Action<TSource, object>> EventAdd<TSource>(string eventName) { var source = typeof(TSource); return source.EventAddImpl<Action<TSource, Action<TSource, object>>>(eventName); } public static Action<object, Action<object, object>> EventAdd(this Type source, string eventName) { return source.EventAddImpl<Action<object, Action<object, object>>>(eventName); }
This is what we need to create delegates for add accessors in different situations. Now we can test them, to make sure if everything is working fine. EventAdd overloads should be called in following way.
var ea1 = DelegateFactory.EventAdd<TestClass, TestClass.PublicEventArgs>("PublicEvent"); var ea2 = DelegateFactory.EventAdd<TestClass, TestClass.InternalEventArgs>("InternalEvent"); var ea3 = DelegateFactory.EventAdd<TestClass>("ProtectedEvent"); var ea4 = Type.EventAdd<TestClass.PublicEventArgs>("PublicEvent"); var ea5 = Type.EventAdd("PrivateEvent");
To test if this delegates works we need to create few handlers.
private static void HandlerWithoutSourceType(object o, TestClass.PublicEventArgs eventArgs) { Console.WriteLine("Public handler without source type works!"); } private static void HandlerWithSourceType(TestClass sender, object eventArgs) { if (eventArgs.GetType() == Type.GetNestedType("ProtectedEventArgs", BindingFlags.Instance | BindingFlags.NonPublic)) { Console.WriteLine("Protected handler works!"); } } private static void TypelessHandler(object sender, object eventArgs) { if (eventArgs is TestClass.PublicEventArgs) { Console.WriteLine("Public handler works!"); } else if (eventArgs is TestClass.InternalEventArgs) { Console.WriteLine("Internal handler works!"); } else if (eventArgs.GetType() == Type.GetNestedType("PrivateEventArgs", BindingFlags.Instance | BindingFlags.NonPublic)) { Console.WriteLine("Private handler works!"); } }
Now we can bind handlers to events using delegates.
ea1(TestInstance, TypelessHandler); ea2(TestInstance, TypelessHandler); ea3(TestInstance, HandlerWithSourceType); ea4(TestInstance, HandlerWithoutSourceType); ea5(TestInstance, TypelessHandler);
This is why handler methods have different set of texts to write in a console – not everyone handles all of events.
TestInstance.InvokePublicEvent(); TestInstance.InvokeInternalEvent(); TestInstance.InvokeProtectedEvent(); TestInstance.InvokePrivateEvent();
After invoking events by special public methods above we will see output in a console like below.
Public handler works! Public handler without source type works! Internal handler works! Protected handler works! Private handler works!
It means that every event handler works, despite different visibility, different signatures of user handlers, custom and default accessors.
Event remove accessors
Best thing about remove accessors are that there have the same signatures as add accessors. Main problems with remove accessors, which is the need for exactly the same reference, is handled in EventHandlerFactory method. In EventRemove methods we just need to change eventInfo.AddMethod to eventInfo.RemoveMethod and pass Boolean.True instead ofBoolean.False to EventHandlerFacory method (to remove unecessary instances of EventHandler from dictionary).
public static Action<TSource, EventHandler<TEventArgs>> EventRemove<TSource, TEventArgs>(string eventName) { var source = typeof(TSource); var eventInfo = GetEventInfo(eventName, source); return (Action<TSource, EventHandler<TEvent>>) eventInfo?.RemoveMethod.CreateDelegate(typeof(Action<TSource, EventHandler<TEvent>>)); } public static Action<object, EventHandler<TEventArgs>> EventRemove<TEventArgs>( this Type source, string eventName) { var eventInfo = GetEventInfo(eventName, source); if (eventInfo != null) { var instanceParameter = Expression.Parameter(typeof(object)); var delegateTypeParameter = Expression.Parameter(typeof(EventHandler<TEventArgs>)); var lambda = Expression.Lambda(Expression.Call(Expression.Convert(instanceParameter, source), eventInfo.RemoveMethod, delegateTypeParameter), instanceParameter, delegateTypeParameter); return (Action<object, EventHandler<TEventArgs>>)lambda.Compile(); } return null; } public static Action<TSource, Action<TSource, object>> EventRemove<TSource>(string eventName) { return typeof(TSource).EventRemoveImpl<Action<TSource, Action<TSource, object>>>(eventName); } public static Action<object, Action<object, object>> EventRemove(this Type source, string eventName) { return source.EventRemoveImpl<Action<object, Action<object, object>>>(eventName); } private static TDelegate EventRemoveImpl<TDelegate>(this Type source, string eventName) where TDelegate : class { var eventInfo = GetEventInfo(eventName, source); if (eventInfo != null) { var eventArgsType = eventInfo.EventHandlerType.GetGenericArguments()[0]; var instanceParameter = Expression.Parameter(typeof(object)); var delegateTypeParameter = Expression.Parameter(typeof(object)); var methodCallExpression = Expression.Call(EventHandlerFactoryMethodInfo.MakeGenericMethod(eventArgsType, source), delegateTypeParameter, Expression.Constant(true)); var lambda = Expression.Lambda(Expression.Call(Expression.Convert(instanceParameter, source), eventInfo.RemoveMethod, methodCallExpression), instanceParameter, delegateTypeParameter); return lambda.Compile() as TDelegate; } return null; }
Code is almost the same. Because of that we can rewrite all those method to build delegate for selected accessor. We just need two constants and extra code for selection of correct accessor MethodInfo.
private const string AddAccessor = "add"; private const string RemoveAccessor = "remove"; public static Action<object, EventHandler<TEventArgs>> EventRemove<TEventArgs>( this Type source, string eventName) { return EventAccessor<TEventArgs>(source, eventName, RemoveAccessor); } public static Action<TSource, EventHandler<TEventArgs>> EventRemove<TSource, TEventArgs>(string eventName) { return EventAccessor<TSource, TEventArgs>(eventName, RemoveAccessor); } public static Action<TSource, Action<TSource, object>> EventRemove<TSource>(string eventName) { return typeof(TSource).EventRemoveImpl<Action<TSource, Action<TSource, object>>>(eventName); } public static Action<object, Action<object, object>> EventRemove(this Type source, string eventName) { return source.EventRemoveImpl<Action<object, Action<object, object>>>(eventName); }
Methods with logic will contain all of previously discussed code.
private static Action<object, EventHandler<TEventArgs>> EventAccessor<TEventArgs> (Type source, string eventName, string accessorName) { var accessor = GetEventInfoAccessor(eventName, source, accessorName); if (accessor != null) { var instanceParameter = Expression.Parameter(typeof(object)); var delegateTypeParameter = Expression.Parameter(typeof(EventHandler<TEventArgs>)); var lambda = Expression.Lambda(Expression.Call(Expression.Convert(instanceParameter, source), accessor, delegateTypeParameter), instanceParameter, delegateTypeParameter); return (Action<object, EventHandler<TEventArgs>>)lambda.Compile(); } return null; } private static Action<TSource, EventHandler<TEventArgs>> EventAccessor<TSource, TEventArgs> (string eventName, string accessorName) { var sourceType = typeof(TSource); var accessor = GetEventInfoAccessor(eventName, sourceType, accessorName); return (Action<TSource, EventHandler<TEventArgs>>) accessor?.CreateDelegate(typeof(Action<TSource, EventHandler<TEventArgs>>)); } private static TDelegate EventAccessorImpl<TDelegate>(Type source, string eventName, string accessorName) where TDelegate : class { var eventInfo = GetEventInfo(eventName, source); if (eventInfo != null) { var accessor = accessorName == AddAccessor ? eventInfo.AddMethod : eventInfo.RemoveMethod; var eventArgsType = eventInfo.EventHandlerType.GetGenericArguments()[0]; var instanceParameter = Expression.Parameter(typeof(object)); var delegateTypeParameter = Expression.Parameter(typeof(object)); var methodCallExpression = Expression.Call(EventHandlerFactoryMethodInfo.MakeGenericMethod(eventArgsType, source), delegateTypeParameter, Expression.Constant(accessorName == RemoveAccessor)); var lambda = Expression.Lambda(Expression.Call(Expression.Convert(instanceParameter, source), accessor, methodCallExpression), instanceParameter, delegateTypeParameter); return lambda.Compile() as TDelegate; } return null; } private static TDelegate EventRemoveImpl<TDelegate>(this Type source, string eventName) where TDelegate : class { return EventAccessorImpl<TDelegate>(source, eventName, RemoveAccessor); }
Ok. Biggest difference is obtaining accessors. New method for that looks like below.
private static MethodInfo GetEventInfoAccessor(string eventName, Type sourceType, string accessor) { var eventInfo = GetEventInfo(eventName, sourceType); return accessor == AddAccessor ? eventInfo?.AddMethod : eventInfo?.RemoveMethod; }
Beside obtaining accessor in EventAccessorImpl method, worth to mention is code for setting flag for EventHandlerFactory.
To test if delegates works we can use them and then raise events – if removing of events works we will not see anything in console. To obtain delegates we can execute following lines.
var er1 = DelegateFactory.EventRemove<TestClass, TestClass.PublicEventArgs>("PublicEvent"); var er2 = DelegateFactory.EventRemove<TestClass, TestClass.InternalEventArgs>("InternalEvent"); var er3 = DelegateFactory.EventRemove<TestClass>("ProtectedEvent"); var er4 = Type.EventRemove<TestClass.PublicEventArgs>("PublicEvent"); var er5 = Type.EventRemove("PrivateEvent");
Calling newly created delegates after previous ones (for add accessors) should remove handlers.
er1(TestInstance, TypelessHandler); er2(TestInstance, TypelessHandler); er3(TestInstance, HandlerWithSourceType); er4(TestInstance, HandlerWithoutSourceType); er5(TestInstance, TypelessHandler); TestInstance.InvokePublicEvent(); TestInstance.InvokeInternalEvent(); TestInstance.InvokeProtectedEvent(); TestInstance.InvokePrivateEvent();
Calling above lines right after test code for add accessors will cause to output test texts in console only one time. It means it works. Everything is fine. 🙂
Summary
In this article we covered how and why create delegates for generic methods (static and instance) and events. Along with previous two articles we covered all members, and now have fast way to access all them without caring for visibility or performance of reflection.
Code for this article and previous ones can be found on github and as Nuget package.