This is second 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 article and looking for detailed explanation of how delegates works, how they are created and why, I strongly encourage you to read it first. If you are just looking for a way to create delegates for methods or constructors it is not necessary.
Last article in series you can find here.
Code with new features and with bug fixes is available on GitHub and as Nuget package.
In previous article and first one in series, we covered:
- Static
- Properties
- Fields
- Instance
- Properties
- Fields
- Indexers
Now it is time to cover following members.
- Static
- Methods
- Instance
- Methods
- Constructors
First members we will discuss are constructors.
Constructors
Of course we are talking about non-static constructors. Yes the static ones also exists, but they are special way of initialization of types not objects. They are called automatically on first use of type and we do not need to do that ourselves and we certainly do not need delegates for that.
Any type can have any number of constructors, so the first thing we need is a way to indicate which one we need to call inside a delegate. Since constructors can differ by parameters types, we can do this in a similar way as with indexers - with set of types for parameters that indicates clearly, which one it should be. Of course very often types do not have any special constructor (declared by user). For this cases we will look for default constructor, without parameters. Since this case is less complex we will discuss this first.
What we need to start? Since every type have at least one constructor, even if it is not defined explicitly, we do not need to add any code to our TestClass
(from previous article). But, as before with other members, we have to add separate method to obtain constructors by reflection.
private static ConstructorInfo GetConstructorInfo(Type source, Type[] types)
{
return (source.GetConstructor(BindingFlags.Public, null, types, null) ??
source.GetConstructor(BindingFlags.NonPublic, null, types, null)) ??
source.GetConstructor(
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, null,
types, null);
}
Logic behind above code is explained in details in previous article for other types of members. Quick explanation: it looks for public, then private and protected and last internal constructors.
Now we can implement a method, that will create delegate for default constructor. This new method in DelegateFactory
will be called DefaultConstructor
.
public static Func<TSource> DefaultContructor<TSource>()
{
var source = typeof(TSource);
var constructorInfo = GetConstructorInfo(source, Type.EmptyTypes);
if (constructorInfo == null)
{
return null;
}
return (Func<TSource>)Expression.Lambda(Expression.New(constructorInfo)).Compile();
}
As you can see it is very similar to PropertyGet
method from previous article. First, constructor is obtained from type and then after null check it is passed to Expression.New
method that creates expression that call this constructor. After that, value is returned.
Above method should be called in following way.
var dc = DelegateFactory.DefaultContructor<TestClass>();
var instance = dc();
It works like any other method, that do not take parameters and returns value, which in this case is our new instance.
How about case when we do not have access to type this way? We can write extension method for Type
type, that returns an object.
public static Func<object> DefaultContructor(this Type source)
{
var constructorInfo = GetConstructorInfo(source, Type.EmptyTypes);
if (constructorInfo == null)
{
return null;
}
return (Func<object>)Expression.Lambda(Expression.New(constructorInfo)).Compile();
}<br /><br />var dc3 = Type.DefaultContructor()
Produced delegate is called in exactly the same way. In case you are wondering why there is no casting of return value to object, it is because source type is always a class type - structures cannot have parameterless constructors. You can easily test it by getting list of constructors of i.e. int
type - no constructors returned by reflection.
Both of above methods creates delegates that can be converted to following lambda.
Func<TestClass> d = () => new TestClass();
Now we should discuss more complicated constructors, with parameters. Let us create few test constructors in TestClass
class.
internal TestClass(int p)
: this()
{
}
protected TestClass(bool p)
: this()
{
}
private TestClass(string p)
: this()
{
}
Ideally, would be to call new overload with source type parameter (or parameter with source type) and parameter with list of types representing collection of constructor parameters.
Func<int,TestClass> c = DelegateFactory.Contructor<TestClass, int>>();
Problem with above is, that C# is not C++ and you cannot have class that takes unspecified number of type parameters. So either we can create separate overload for every number of parameters (up to limit of Func
class) or write a little less convenient overload, but much more universal.
var c = DelegateFactory.Contructor<TestClass, Func<int, TestClass>>();
Returned variable is the same. In previous example we could also write var
keyword instead of explicit type of delegate, but it would be less readable why we cannot do that. Difference between first and second version is, that in second we have explicit return type passed. How we can obtain types of parameters then? It is very easy with reflection.
private static Type[] GetFuncDelegateArguments<TDelegate>() where TDelegate : class
{
return typeof(TDelegate).GenericTypeArguments.Reverse().Skip(1).Reverse().ToArray();
}
Above function retrieves parameters from delegates represented by Func
class. Reversing and skipping of one type parameter is performed to omit last one, which is always return type. For above exemplary calls (Func<TestClass,int>
) it will return {typeof(int)}
, array with single element - type of integral number, Int32
.
If you look close enough on code, you can notice, that TestClass
type parameter
is passed twice: on its own and as return type of constructor delegate. It is not necessary and we can remove first one by obtaining source type with defined constructor from passed delegate type. It can be very easily done by below method.
private static Type GetFuncDelegateReturnType<TDelegate>() where TDelegate : class
{
return typeof(TDelegate).GenericTypeArguments.Last();
}
Now we can implement first overload for constructors with parameters.
public static TDelegate Contructor<TDelegate>() where TDelegate : class
{
var source = GetFuncDelegateReturnType<TDelegate>();
var ctrArgs = GetFuncDelegateArguments<TDelegate>();
var constructorInfo = GetConstructorInfo(source, ctrArgs);
if (constructorInfo == null)
{
return null;
}
var parameters = ctrArgs.Select(Expression.Parameter).ToList();
return Expression.Lambda(Expression.New(constructorInfo, parameters), parameters).Compile() as TDelegate;
}
Obviously, set of type parameters in TDelegate
type have to be correct for this to work, but it is exactly the same when you use reflection - you have to pass correct types to obtain MethodInfo
and then correct parameters to call method.
For example for TestClass
constructor with int
parameter above Constructor
method would create delegate, that can be represented by below lambda.
Func<int, TestClass> d = i => new TestClass(i);
Main difference from DefaultContructor
method is set of parameters in both: Expression.New
and Expression.Lambda
calls, created from array of types passed in TDelegate
as type arguments.
Good thing about that mechanism is that, it will work as well for parameterless constructor and we can use this new method to rewrite DefaultContructor
method.
public static Func<TSource> DefaultContructor<TSource>()
{
return Contructor<Func<TSource>>();
}
This should return delegate without parameters that returns instance, so Func<TSource>
is passed as delegate type. But how about a case when we do not know source type and we want a delegate for constructor with parameters? We have to change Constructor
method so it returns a delegate with return type of object. Because of that, we cannot acquire source type from delegate so we have to pass it differently.
public static TDelegate Contructor<TDelegate>(this Type source)
where TDelegate : class
{
var ctrArgs = GetFuncDelegateArguments<TDelegate>();
var constructorInfo = GetConstructorInfo(source, ctrArgs);
if (constructorInfo == null)
{
return null;
}
var parameters = ctrArgs.Select(Expression.Parameter).ToList();
Expression returnExpression = Expression.New(constructorInfo, parameters);
if (!source.IsClass)
{
returnExpression = Expression.Convert(returnExpression, typeof(object));
}
return Expression.Lambda(returnExpression, parameters).Compile() as TDelegate;
}
There are few points that might need to be clarified. It is almost the same as previous one, but it do returns an object instead of source type instance. If type to construct is value type it is converted to object before returning final value. Safe cast at the end, forces user to pass matching delegate.
If we would write structure like this:
public struct TestStruct
{
public TestStruct(int i)
{
}
}
Above method called would produce delegate very similar to the one below.
var c = typeof(TestStruct).Contructor<Func<int, object>>();
Func<int, object> d = i => (object)new TestStruct(i);
Next problem arise when we do not have access to all types of parameters - not known type methods can have parameters that are also unknown or unavailable. This is similar problem to the one, that forced us to create Constructor<TDelegate>
overload - we cannot really know what type of delegate should be constructed if we do not know types of arguments. Of course we could create method that returns delegate with correct number of parameters as in constructor, except with not known types changed to object. But it stills limits us to number of parameters allowed in Func
class (however if code have methods with that many parameters there are probably more important problems than this ) and it could return constructor different than expected (there can be more than one with given number of parameters). So instead we should write more general method that takes array of types for constructor, exactly how this was done with indexers in the previous article.
public static Func<object[], object> Contructor(this Type source, params Type[] ctrArgs)
{
var constructorInfo = GetConstructorInfo(source, ctrArgs);
if (constructorInfo == null)
{
return null;
}
var argsArray = Expression.Parameter(typeof(object[]));
var paramsExpression = new Expression[ctrArgs.Length];
for (var i = 0; i < ctrArgs.Length; i++)
{
var argType = ctrArgs[i];
paramsExpression[i] =
Expression.Convert(Expression.ArrayIndex(argsArray, Expression.Constant(i)), argType);
}
Expression returnExpression = Expression.New(constructorInfo, paramsExpression);
if (!source.IsClass)
{
returnExpression = Expression.Convert(returnExpression, typeof(object));
}
return (Func<object[], object>)Expression.Lambda(returnExpression, argsArray).Compile();
}
It returns delegate that takes array of objects and returns object. Objects are converted to correct type of parameter at the same index. Collections of processed parameters is then used as a collection of arguments for constructor. Created instance is then converted to object if is of value type. This will look much clearer as a lambda for constructor with single parameter.
Func<object[], object> d = args => (object)new TestStruct((int)args[0]);
We should test those methods now. Below lines creates delegates and then creates instances via those delegates.
var cd = DelegateFactory.DefaultContructor<TestClass>();
var cd1 = Type.DefaultContructor();
var cd2 = Type.Contructor<Func<object>>();
var t_ = cd();
var t1 = cd1();
var t2 = cd2();
var c1 = DelegateFactory.Contructor<Func<TestClass>>();
var c2 = DelegateFactory.Contructor<Func<int, TestClass>>();
var c3 = DelegateFactory.Contructor<Func<bool, TestClass>>();
var c4 = DelegateFactory.Contructor<Func<string, TestClass>>();
var c5 = DelegateFactory.Contructor<Func<int, TestClass>>();
var c6 = Type.Contructor(typeof(int));
var c7 = typeof(TestStruct).Contructor<Func<int, object>>();
var c8 = typeof(TestStruct).Contructor(typeof(int));
var t3 = c1();
var t4 = c2(0);
var t5 = c3(false);
var t6 = c4("");
var t7 = c5(0);
var t8 = c6(new object[] { 0 });
var t9 = c7(0);
var t10 = c8(new object[] { 0 });
If you are wondering what is Type
variable above - it is instance of TestClass
, which is class used for testing all of delegates. Both are explained in the previous article in series. You can also look for a code for this article (and previous one) on github for more information.
All variables with names starting with 't' will have value of newly created instance. Constructors called via delegates c1
, c2
, c3
and c4
have different visibility (public, internal, protected and private) and it all works without problems. It works but how fast is it?
_stopWatch = new Stopwatch();
_stopWatch.Start();
for (var i = 0; i < _delay; i++)
{
var test = new TestClass();
}
_stopWatch.Stop();
Console.WriteLine("Public default constructor directly: {0}", _stopWatch.ElapsedMilliseconds);
var constructorInfo = Type.GetConstructor(Type.EmptyTypes);
_stopWatch = new Stopwatch();
_stopWatch.Start();
for (var i = 0; i < _delay; i++)
{
var test = (TestClass)constructorInfo.Invoke(null);
}
_stopWatch.Stop();
Console.WriteLine("Public default constructor via reflection: {0}", _stopWatch.ElapsedMilliseconds);
_stopWatch = new Stopwatch();
_stopWatch.Start();
for (var i = 0; i < _delay; i++)
{
var test = Activator.CreateInstance();
}
_stopWatch.Stop();
Console.WriteLine("Public default constructor via Activator: {0}", _stopWatch.ElapsedMilliseconds);
_stopWatch = new Stopwatch();
_stopWatch.Start();
for (var i = 0; i < _delay; i++)
{
var t = c1();
}
_stopWatch.Stop();
Console.WriteLine("Public default constructor proxy: {0}", _stopWatch.ElapsedMilliseconds);
_stopWatch = new Stopwatch();
_stopWatch.Start();
for (var i = 0; i < _delay; i++)
{
var test = c5(0);
}
_stopWatch.Stop();
Console.WriteLine("Public constructor with parameter proxy: {0}", _stopWatch.ElapsedMilliseconds);
_stopWatch = new Stopwatch();
_stopWatch.Start();
for (var i = 0; i < _delay; i++)
{
c6(new object[] {0});
}
_stopWatch.Stop();
Console.WriteLine("Public constructor with parameter via array proxy: {0}", _stopWatch.ElapsedMilliseconds);
After running this we will see results like below.
Public default constructor directly: 14896
Public default constructor via reflection: 33763
Public default constructor via Activator: 21690
Public default constructor proxy: 14923
Public constructor with parameter proxy: 15261
Public constructor with parameter via array proxy: 16768
As you can see difference is very small between calling default constructor directly and by delegate, because it is call to the same method without any intermediate call. Activator
is significantly slower, which is expected since code in Activator
class searches for correct constructor in type (by given parameters to CreateInstance
method) and then calls it. It takes times if it is done so many times. Reflection is more than two times slower. Last two delegates are little slower, but not so much (3-12%). Difference would be bigger with empty or less complicated constructor (TestClass
constructors have initialization of fields and properties), but even then it involves less calculation (in comparison to the reflection or Activator
) with just few casts and indexing operations.
Now when we have constructors covered we can switch to even more important member type: methods.
Static methods
After the properties covered in the first article, it is time for methods, which are also one of the most used type of members. With experience gained by dealing with constructors, methods should not be a problem . Static ones first.
Main difference between static methods and constructors is that constructors have fixed return type, which is the same as source type. With static methods we have to be more flexible, so we need one extra parameter for all delegate-creation methods. Similar to constructors we will write three overloads for static methods: static method with source type in type parameter, Type
extension method with type of delegate in type parameter, and Type
extension method with list of parameters type of desired static method overload (if there are more than one with the same name). It will be much clearer in form of a code.
var sm1 = DelegateFactory.StaticMethod<TestClass, Func<string, string>>("StaticPrivateMethod");
var sm2 = Type.StaticMethod<Func<string, string>>("StaticPublicMethod");
var sm3 = Type.StaticMethod("StaticPublicMethod", typeof(string));
Those three overloads should cover all cases: when we know all types (source class type, parameters types, and return type of method), when we do not know source type, but we know parameters types, and when we do not know any types.
There is one complication (comparing to constructor): methods, static and instance, do not have to have return type at all (or to be more precise return type is equal to typeof(void)
). So first and second overload should work also with Action
delegates.
var sm1 = DelegateFactory.StaticMethod<TestClass, Action<string, string>>("StaticPublicMethod");
var sm2 = Type.StaticMethod<Action<string, string>>("StaticPublicMethod");
With third overload it is more complex case. It would be awesome if it would produce different delegate (Action
or Func
) if static method is void or not. It is possible, but not very convenient. Look at following code.
var sm1 = (Action<object[]>)Type.StaticMethod("StaticPublicMethod", typeof(string));
var sm2 = (Func<object[], object>)Type.StaticMethod("StaticPublicMethod", typeof(string));
To make produced delegate possible to invoke, we would have to cast it to correct type, so embedding information about type of delegate into code is necessary anyway. Thing is, to use any static method of any class we have to be aware of its signature anyway, to be able to write code that uses this method. So it is much more convenient to write two separate methods for creating static method delegate when we do not know types of source and parameters (at compile-time), for void and not void methods.
var sm1 = Type.StaticMethod("StaticPublicMethod", typeof(string));
var sm2 = Type.StaticMethodVoid("StaticPublicMethodVoid", typeof(string));
First delegate is for Func
and second is for Action
delegate. If user of DelegateFactory
have to be aware of static method signature, this way it is much more convenient to use.
Before implementing above we have to write a method for obtaining static method MethodInfo
object, just like for constructors.
private static MethodInfo GetStaticMethodInfo(Type source, string name, Type[] types)
{
var methodInfo = (source.GetMethod(name, BindingFlags.Static, null, types, null) ??
source.GetMethod(name, BindingFlags.Static | BindingFlags.NonPublic, null, types, null)) ??
source.GetMethod(name, BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public, null, types, null);
return methodInfo;
}
Very similar code as previous members types. We are looking for method with specified name with flags set to static member and different visibility of a method.
Now we can implement StaticMethod
overloads.
public static TDelegate StaticMethod<TSource, TDelegate>(string name)
where TDelegate : class
{
return typeof(TSource).StaticMethod<TDelegate>(name);
}
public static TDelegate StaticMethod<TDelegate>(this Type source,
string name)
where TDelegate : class
{
var paramsTypes = GetFuncDelegateArguments<TDelegate>();
var methodInfo = GetStaticMethodInfo(source, name, paramsTypes);
return methodInfo.CreateDelegate(typeof(TDelegate)) as TDelegate;
}
Nothing complicated here. Just delegate creation from MethodInfo
object, like in properties, indexers and constructors. First overload is just call to a second one. Of course TDelegate
have to be compatible with static method signature. The same is required if you want to call method by reflection.
If we add new test method to TestClass
, delegates created by above StaticMethod
methods would be similar to following lambda statement.
public static string StaticPublicMethod(string s)
{
return s;
}
Func<string, string> d = s1 => TestClass.StaticPublicMethod(s1);
Third overload in form of StaticMethod
and StaticMethodVoid
is a little more complicated and, in the same way as a constructor called by objects in this article and other members in the previous one, it requires expressions to make it work.
public static Func<object[], object> StaticMethod(this Type source,
string name, params Type[] paramsTypes)
{
var methodInfo = GetStaticMethodInfo(source, name, paramsTypes);
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 (!source.IsClass)
{
returnExpression = Expression.Convert(returnExpression, typeof(object));
}
return (Func<object[], object>)Expression.Lambda(returnExpression, argsArray).Compile();
}
public static Action<object[]> StaticMethodVoid(this Type source,
string name, params Type[] paramsTypes)
{
var methodInfo = GetStaticMethodInfo(source, name, paramsTypes);
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);
return (Action<object[]>)Expression.Lambda(returnExpression, argsArray).Compile();
}
If you are wondering if those methods can be rewritten to share code, you are right. They can and without loss of performance, like with properties setters. We can check if return type of method is void
and omit casting to object and finally, cast delegate to return type from type parameter.
public static Func<object[], object> StaticMethod(this Type source,
string name, params Type[] paramsTypes)
{
return StaticMethod<Func<object[], object>>(source, name, paramsTypes);
}
public static Action<object[]> StaticMethodVoid(this Type source,
string name, params Type[] paramsTypes)
{
return StaticMethod<Action<object[]>>(source, name, paramsTypes);
}
public static TDelegate StaticMethod<TDelegate>(this Type source,
string name, params Type[] paramsTypes)
where TDelegate : class
{
var methodInfo = GetStaticMethodInfo(source, name, paramsTypes);
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;
}
We should create a few new methods in TestClass
to be able to test if everything works fine.
public static string StaticPublicMethod(int i)
{
return i.ToString();
}
public static string StaticPublicMethodVoidParameter;
public static void StaticPublicMethodVoid(string s)
{
StaticPublicMethodVoidParameter = s;
}
internal static string StaticInternalMethod(string s)
{
return s;
}
protected static string StaticProtectedMethod(string s)
{
return s;
}
private static string StaticPrivateMethod(string s)
{
return s;
}
For StaticPublicMethod
and StaticPublicMethodVoid
created delegates can be represented by lambdas like below.
Func<object[], object> d1 = os => TestClass.StaticPublicMethod((string)os[0]);
Action<object[]> d2 = os => TestClass.StaticPublicMethodVoid((string)os[0]);
If return type of StaticPublicMethod
would be value type, lambda return value would be also converted to object.
Now we can test if everything works in console application. Let us add few lines to Main
method of program.
var sm1 = DelegateFactory.StaticMethod<TestClass, Func<string, string>>("StaticPublicMethod");
var sm2 = DelegateFactory.StaticMethod<TestClass, Func<string, string>>("StaticInternalMethod");
var sm3 = DelegateFactory.StaticMethod<TestClass, Func<string, string>>("StaticProtectedMethod");
var sm4 = DelegateFactory.StaticMethod<TestClass, Func<string, string>>("StaticPrivateMethod");
var sm5 = Type.StaticMethod<Func<string, string>>("StaticPublicMethod");
var sm6 = Type.StaticMethod<Func<string, string>>("StaticInternalMethod");
var sm7 = Type.StaticMethod<Func<string, string>>("StaticProtectedMethod");
var sm8 = Type.StaticMethod<Func<string, string>>("StaticPrivateMethod");
var sm9 = Type.StaticMethod("StaticPublicMethod", typeof(string));
var sm10 = Type.StaticMethodVoid("StaticPublicMethodVoid", typeof(string));
var sm11 = Type.StaticMethod("StaticInternalMethod", typeof(string));
var sm12 = Type.StaticMethod("StaticProtectedMethod", typeof(string));
var sm13 = Type.StaticMethod("StaticPrivateMethod", typeof(string));
var sm14 = DelegateFactory.StaticMethod<TestClass, Func<int, int>>("StaticPublicMethodValue");
var sm15 = Type.StaticMethod<Func<int, int>>("StaticPublicMethodValue");
var sm16 = Type.StaticMethod("StaticPublicMethodValue", typeof(int));
var t = sm1("test");
var t2 = sm2("test");
var t3 = sm3("test");
var t4 = sm4("test");
var t5 = sm5("test");
var t6 = sm6("test");
var t7 = sm7("test");
var t8 = sm8("test");
var t9 = sm9(new object[] { "test" });
sm10(new object[] { "test" });
var t10 = TestClass.StaticPublicMethodVoidParameter;
var t11 = sm11(new object[] { "test" });
var t12 = sm12(new object[] { "test" });
var t13 = sm13(new object[] { "test" });
var t14 = sm14(0);
var t15 = sm15(0);
var t16 = sm16(new object[] { 0 });
Lots of lines with really simple logic behind them. Four first lines creates delegates by overload of StaticMethod
method with parameter types, so created delegates have full knowledge of methods signature. Next four lines produces delegates using source type from Type
instance - also signatures of static methods are retained. After that, we have two lines, that takes set of parameter types and then creates delegates that have single parameter no matter of number of parameters of target static method. They differs only by return type of created delegate (void or object). Next three works the same way, but for non-public methods. Last three tests all StaticMethod
overloads, but for method that returns value type instead of reference type. Second part of above test just calls created delegates to make sure they works. Below image shows values returned by those calls.
Returned values are what they supposed to be, according to definitions of static methods in TestClass
.
The last thing to do about static methods is to test performance of delegates.
_stopWatch = new Stopwatch();
_stopWatch.Start();
for (var i = 0; i < _delay; i++)
{
var test = TestClass.StaticPublicMethod("test");
}
_stopWatch.Stop();
Console.WriteLine("Static Public method directly: {0}", _stopWatch.ElapsedMilliseconds);
_stopWatch = new Stopwatch();
_stopWatch.Start();
for (var i = 0; i < _delay; i++)
{
var test = sm1("test");
}
_stopWatch.Stop();
Console.WriteLine("Static Public method proxy: {0}", _stopWatch.ElapsedMilliseconds);
_stopWatch = new Stopwatch();
_stopWatch.Start();
for (var i = 0; i < _delay; i++)
{
var test = sm9(new object[] { "test" });
}
_stopWatch.Stop();
Console.WriteLine("Static Public method via proxy with array: {0}", _stopWatch.ElapsedMilliseconds);
var methodInfo = Type.GetMethod("StaticPublicMethod", new[] { typeof(string) });
_stopWatch = new Stopwatch();
_stopWatch.Start();
for (var i = 0; i < _delay; i++)
{
var test = methodInfo.Invoke(null, new object[] { "test" });
}
_stopWatch.Stop();
Console.WriteLine("Static Public method via reflection: {0}", _stopWatch.ElapsedMilliseconds);
Result in console application prove again that delegates are much faster than reflection.
Static Public method directly: 1160
Static Public method proxy: 1167
Static Public method via proxy with array: 2930
Static Public method via reflection: 21317
As you can see calling method by delegate is just a little slower. More thorough tests shows that it may varied depending on method signature and body, but still it should be only slower by few percent. Calls by array of object are obviously slower because, of all the casting and indexing; in above example it takes 2.5 times longer, but it should be less for more complicated method than just single return statement. Last line shows that reflection is much more slower, as expected (about 18 times).
The last thing to do is to add check if methodInfo
variable is null to prevent errors. It works the same as with constructors: null propagation operator ?
for overload with only reflection and if
statement for overload with expressions.
Now we can jump to instance methods.
Instance methods
Instance methods will have very similar overloads except for StaticMethod<TSource, TDelegate>(string name),
which is just not necessary since with instance methods we can obtain TSource
from first parameter of TDelegate
(similarly to properties). Instance methods are like static methods that takes first parameter with target instance, which give meaning to this
keyword inside method body. Easiest way to understand it is to think of instance method as extension methods. For example for test method like below:
public string PublicMethod(string s)
{
return s;
}
we can create delegate that could look like following static extension method.
public string Delegate(this TestClass @this, string s)
{
return @this.PublicMethod(s);
}
It works the same like with instance properties, fields and indexers in the previous article.
Ok, let us implements overloads of InstanceMethod
methods. First, will be the one with all types known (source, parameters and return type).
public static TDelegate InstanceMethod<TDelegate>(string name)
where TDelegate : class
{
var paramsTypes = GetFuncDelegateArguments<TDelegate>();
var source = paramsTypes.First();
paramsTypes = paramsTypes.Skip(1).ToArray();
var methodInfo = GetMethodInfo(source, name, paramsTypes);
return methodInfo?.CreateDelegate(typeof(TDelegate)) as TDelegate;
}
Since we know all types, we also have to know signature of method and this signature is passed as TDelegate
type to InstanceMethod
method. Consider PublicMethod
mentioned above. For this method TDelegate
should look like below.
Func<TestClass, string, string>
This method takes two parameters (instance, on which method should be called and target method parameter) and returns string. Rest of code is very similar to Constructor
method. We take type parameters from TDelegate
, first one is used as source type and rest to search for correct overload of target method in source type. After that delegate is created like before with other members types. Example call to this overload should look like this.
var m1 = DelegateFactory.InstanceMethod<Func<TestClass, string, string>>("PublicMethod");
With other members types we always discussed overload, when we do not want to pass source type as type parameter, but instead we pass it as first parameter (this
) of extension method. Thing is, with instance methods we have the same problem like with constructors: we cannot create overload that takes unspecified number of type parameters and returns delegate with unspecified number of parameters. It forces us to pass delegate type or write separate method for every number of parameters. Passing delegate causes source type to be specified twice. If we want to write similar extension method, like with other members, we should allow delegate with first parameter as object
instead of source type. This makes things a little more complicated, but it will make DelegateFactory
more concise.
public static TDelegate InstanceMethod<TDelegate>(this Type source, string name)
where TDelegate : class
{
var delegateParams = GetFuncDelegateArguments<TDelegate>(true);
var methodInfo = GetMethodInfo(source, name, delegateParams);
if (methodInfo == null)
{
return null;
}
Delegate deleg;
if (delegateParams[0] == 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;
}
It is a lot of code, but is not that much complicated. If first parameter of delegate (either source instance, object or any other compatible type) is equal source
parameter it works exactly as previous overload. If not, it performs delegate creation via expression, very similar to respective Constructor
overload. List of parameters types returned from TDelegate
is used to create ExpressionsParameter
collection for Expression.Call
. The same collection (with source parameter as object) is used as parameters set for Expression.Lambda
. If
statement casts returned value to object only if it is structure and if target method do not return void.
For TestClass.PublicMethod
method, above overload will create delegate similar to below lambdas.
Func<TestClass, string, string> d1 = (i, s) => i.PublicMethod(s);
Func<object, string, string> d2 = (i, s) => ((TestClass)i).PublicMethod(s);
There are two versions depending if we call it with delegate type with correct instance type or with just an object.
Now, we can discuss third overload that takes only objects (in two versions: for void and non-void methods) exactly the same way as with static methods.
public static TDelegate InstanceMethod<TDelegate>(this Type source,
string name, params Type[] paramsTypes)
where TDelegate : class
{
var methodInfo = GetMethodInfo(source, name, paramsTypes);
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;
}
This overload is almost the same as similar one for static methods. The only thing that differs is one more parameter with instance on which method should be called. Beside that it is the same: get method info, check for null, cast array of params to their types, call target method, cast returned value if needed. For TestClass.PublicMethod
it will create delegate similar to below lambda.
Func<object, object[], object> d = (o, p) => ((TestClass)o).PublicMethod((string)p[0]);
With all methods written we can test if everything works and check performance of this solution. As before, with other members, we can do it for all visibilities of methods (public, internal, protected and private).
public string PublicMethodVoidParameter;
public string PublicMethod(string s)
{
return s;
}
public void PublicMethodVoid(string s)
{
PublicMethodVoidParameter = s;
}
internal string InternalMethod(string s)
{
return s;
}
protected string ProtectedMethod(string s)
{
return s;
}
private string PrivateMethod(string s)
{
return s;
}
Test are very similar to tests of static methods.
var m1 = DelegateFactory.InstanceMethod<Func<TestClass, string, string>>("PublicMethod");
var m2 = DelegateFactory.InstanceMethod<Func<TestClass, string, string>>("InternalMethod");
var m3 = DelegateFactory.InstanceMethod<Func<TestClass, string, string>>("ProtectedMethod");
var m4 = DelegateFactory.InstanceMethod<Func<TestClass, string, string>>("PrivateMethod");
var m5 = Type.InstanceMethod<Func<TestClass, string, string>>("PublicMethod");
var m6 = Type.InstanceMethod<Func<object, string, string>>("PublicMethod");
var m7 = Type.InstanceMethod("PublicMethod", typeof(string));
var m8 = Type.InstanceMethodVoid("PublicMethodVoid", typeof(string));
var t = m1(TestInstance, "test");
var t2 = m2(TestInstance, "test");
var t3 = m3(TestInstance, "test");
var t4 = m4(TestInstance, "test");
var t5 = m5(TestInstance, "test");
var t6 = m6(TestInstance, "test");
var t7 = m7(TestInstance, new object[] { "test" });
m8(TestInstance, new object[] { "test" });
var t8 = TestInstance.PublicMethodVoidParameter;
The first four above lines tests different methods with different visibility. Next two, tests the same overload once with correct instance type and second time with object. Last two creates delegates with only objects for void and non-void method.
As you can see every created delegate works just fine and returned variables have expected values. Similar code, that was used to test performance of delegates to static methods, can be adjusted to test performance of instance ones.
_stopWatch = new Stopwatch();
_stopWatch.Start();
for (var i = 0; i < _delay; i++)
{
var test = TestInstance.PublicMethod("test");
}
_stopWatch.Stop();
Console.WriteLine("Public method directly: {0}", _stopWatch.ElapsedMilliseconds);
_stopWatch = new Stopwatch();
_stopWatch.Start();
for (var i = 0; i < _delay; i++)
{
var test = m1(TestInstance, "test");
}
_stopWatch.Stop();
Console.WriteLine("Public method proxy: {0}", _stopWatch.ElapsedMilliseconds);
var methodInfo = Type.GetMethod("PublicMethod");
_stopWatch = new Stopwatch();
_stopWatch.Start();
for (var i = 0; i < _delay; i++)
{
var test = methodInfo.Invoke(TestInstance, new object[] { "test" });
}
_stopWatch.Stop();
Console.WriteLine("Public method proxy: {0}", _stopWatch.ElapsedMilliseconds);
Result in console output should be very much like this:
Public method directly: 1199
Public method proxy: 1287
Public method proxy: 23044
As you can see, delegates for instance methods (exactly like with other members) are just a little slower than direct call to method. Reflection is way, way more inefficient and about 19 times slower.
Summary
In this article we covered how and why create delegates for constructors, static and instance methods. We just need to cover events and special type of methods: generic methods. Both static and instance methods can be generic and along with events, they will be covered in next article.
Code for article can be found on github and as Nuget package.
CodeProject