Preface
Imagine that you are using a dynamically created library. Imagine you are constructing a plug-in engine for your application. You will attach assemblies dynamically in both these cases. Then it will be necessary to create instances of some unknown classes from attached assemblies. Maybe you will even need to call some unknown functions of these unknown classes. You can solve a part of such problems with usage of base classes and interfaces. However, several situations require reflection anyway. Unfortunately, it is well known that the reflection is very slow in .NET.
I can give an example of such a situation:
namespace plugin {
public class l5fks9rffn33md : knownParamType
{
}
public class p4sdfm2mlwd5 : knownType
{
public void testAction(l5fks9rffn33md p)
{
}
}
}
We need to do it within our code:
We have knownType
and knownParamType
in our code, but we do not know auto generated types in compile-time. We also cannot define an interface containing testAction
because it uses unknown types in signature.
It is possible to do the necessary actions with:
Type unknownType = Type.GetType("plugin.p4sdfm2mlwd5");
Type unknownParamType = Type.GetType("plugin.l5fks9rffn33md");
knownType instance = (knownType)Activator.CreateInstance(unknownType);
knownParamType param = (knownParamType)Activator.CreateInstance(unknownParamType);
unknownType.GetMethod("testAction").Invoke(instance, new object[] {param});
However, this code is very slow (up to 100 times slower than direct call). In addition, it is not type-safe.
There is also other way: dynamic method, which uses dynamic IL generation with Reflection.Emit
. This way is very complex (and very powerful also). C# 4.0 offers native calling of unknown methods but it is not released yet. I want to present one more method using power of generics only and doing the same things with the same speed as IL generation. It is not as easy as MethodInfo.Invoke
, but not so complex as Reflection.Emit
.
Instance Creation
We want to create an instance of a type having a constructor without parameters. Let's create a generic function doing it:
public static object createNewObj<T>() where T : new()
{
return new T();
}
It is very simple, is it not?
But how to call this function? We cannot write:
Because type p4sdfm2mlwd5
is unknown.
Let's use two rare functions: MethodInfo.MakeGenericMethod
and Delegate.CreateDelegate
:
public static Func<object> MakeNewDlg(Type t)
{
return Delegate.CreateDelegate(typeof(Func<object>),
typeof(Helper).GetMethod("createNewObj").MakeGenericMethod(new Type[]
{ t })) as Func<object>;
}
Note that useful generic delegates Func
and Action
are defined in System
namespace. Now we can call:
Func<object> knownTypeCreator =
Helper.MakeNewDlg(Type.GetType("plugin.p4sdfm2mlwd5"));
knownType instance = (knownType)knownTypeCreator();
It is also good to store knownTypeCreator
somewhere and use it each time you need a new p4sdfm2mlwd5
instance. It will work fast because it is just a delegate
and it uses no reflection.
However, this code is still not type-safe. The next one is better:
public static Func<K> MakeNewDlg<K>(Type t)
{
return Delegate.CreateDelegate(typeof(Func<K>),
typeof(Helper).GetMethod("createNewObj2").MakeGenericMethod(new Type[]
{ t, typeof(K) })) as Func<K>;
}
public static K createNewObj2<T, K>() where T : K, new()
{
return new T();
}
It returns a typed delegate
and we can omit type cast:
Func<knownType> knownTypeCreator = Helper.MakeNewDlg <knownType>
(Type.GetType("plugin.p4sdfm2mlwd5"));
knownType instance = knownTypeCreator();
Summary: we have any generators and can create as many instances as we need. The next step is to call methods. MakeGenericMethod
will help us again.
Method Calling
Delegate.CreateDelegate
function allows creating static
delegates for non-static methods. It is the greatest ability to reuse the same delegate
with different object instances like this:
class X
{
void test() { }
}
…
Action<X> caller = (Action<X>)Delegate.CreateDelegate(typeof(Action<X>),
typeof(X).GetMethod("test"));
X a = new X();
X b = new X();
caller(a);
caller(b);
Seems, we can use the same way for our purposes:
MethodInfo method = Type.GetType("p4sdfm2mlwd5").GetMethod("testAction");
Action<knownType, knownParamType> d =
(Action<knownType, knownParamType>)Delegate.CreateDelegate
(typeof(Action<knownType, knownParamType>), method);
Ow! This code generates a runtime error! It occurs because a newly created delegate
should have the same signature as the underlying method (actually, it can return base type as result type but it is not allowed to use base type as parameter, see MSDN for details). In addition, the target type should have this method.
We can solve this problem with an anonymous method:
Action<p4sdfm2mlwd5, l5fks9rffn33md> tmp =
(Action<p4sdfm2mlwd5, l5fks9rffn33md>)Delegate.CreateDelegate
(typeof(Action<p4sdfm2mlwd5, l5fks9rffn33md>), method);
Action<knownType, knownParamType> d = delegate
(knownType target, knownParamType p) { tmp((p4sdfm2mlwd5)target,
(l5fks9rffn33md)p); };
There is no problem with CreateDelegate
now, there is another problem. This code cannot be compiled because it uses unknown types.
Let's surround it with a generic function eliminating bad types.
public static Action<knownT, knownP1> A1<T, P1, knownT, knownP1>(MethodInfo method)
where T : knownT
where P1 : knownP1
{
Action<T, P1> d = (Action<T, P1>)Delegate.CreateDelegate
(typeof(Action<T, P1>), method);
return delegate(knownT target, knownP1 p) { d((T)target, (P1)p); };
}
Note, I've named the function "A1
". It will have a special meaning when we will search for it by name.
One more thing remains: we should call this nice function with the same way as createNewObj
(do you remember it?). Calling code is more complex because it should support different parameters count.
public static T MakeCallDlg<T>(MethodInfo method)
{
string creatorName = (method.ReturnParameter.ParameterType ==
typeof(void) ? "A" : "F") + method.GetParameters().Length.ToString();
List<Type> signature = new List<Type>();
signature.Add(method.DeclaringType);
foreach (ParameterInfo pi in method.GetParameters())
{
signature.Add(pi.ParameterType);
}
signature.AddRange(typeof(T).GetGenericArguments());
return (T)typeof(Helper).GetMethod(creatorName).MakeGenericMethod
(signature.ToArray()).Invoke(null, new object[] { method });
}
You should prepare several functions like A0, A1, A2
, etc, F0, F1, F2, etc
for any parameters count you need. MakeCallDlg
will select the necessary generic function and fill it with generic parameters.
Now we can call testAction
:
Type unknownType = Type.GetType("plugin.p4sdfm2mlwd5");
Type unknownParamType = Type.GetType("plugin.l5fks9rffn33md");
MethodInfo test = unknownType.GetMethod("testAction");
Func<knownType> unknownTypeCreator =
Helper.MakeNewDlg<knownType>(unknownType);
Func<knownParamType> unknownParamTypeCreator =
Helper.MakeNewDlg<knownParamType>(unknownParamType);
Action<knownType, knownParamType> methodCaller =
Helper.MakeCallDlg<Action<knownType, knownParamType>>(test);
knownType instance = unknownTypeCreator();
knownParamType param = unknownParamTypeCreator();
methodCaller(instance, param);
And again, and again…
methodCaller(unknownTypeCreator(),unknownParamTypeCreator());
methodCaller(unknownTypeCreator(),unknownParamTypeCreator());
methodCaller(unknownTypeCreator(),unknownParamTypeCreator());
I also created F0
and F1
. You can add any other functions you need in the same way.
public static Func<knownT, knownR> F0<T, knownT, knownR>(MethodInfo method)
where T : knownT
{
Func<T, knownR> d = (Func<T, knownR>)Delegate.CreateDelegate
(typeof(Func<T, knownR>), method);
return delegate(knownT target) { return d((T)target); };
}
public static Func<knownT, knownP1, knownR> F1<T, P1, knownT, knownP1, knownR>
(MethodInfo method)
where T : knownT
where P1 : knownP1
{
Func<T, P1, knownR> d = (Func<T, P1, knownR>)Delegate.CreateDelegate
(typeof(Func<T, P1, knownR>), method);
return delegate(knownT target, knownP1 p) { return d((T)target, (P1)p); };
}
You also can access unknown properties with Get
and Set
methods. These methods are available with:
unknownType.GetProperty("testProperty").GetGetMethod();
unknownType.GetProperty("testProperty").GetSetMethod();
Conclusion
I've compared performance of different calling types (see benchmark code below):
- Direct call - 2200
- My way - 7400
MethodInfo.Invoke
- 192000
Here is the full source code of the example:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
#region autogenerated code
namespace plugin {
public class l5fks9rffn33md : knownParamType
{
}
public class p4sdfm2mlwd5 : knownType
{
private l5fks9rffn33md field;
public void testAction(l5fks9rffn33md p)
{
field = p;
}
public l5fks9rffn33md testFunction(l5fks9rffn33md p)
{
return p;
}
}
}
#endregion
#region known types
public class knownParamType
{
}
public class knownType
{
}
#endregion
class Helper
{
private static Action<knownT, knownP1> A1<T, P1, knownT,
knownP1>(MethodInfo method)
where T : knownT
where P1 : knownP1
{
Action<T, P1> d = (Action<T, P1>)Delegate.CreateDelegate
(typeof(Action<T, P1>), method);
return delegate(knownT target, knownP1 p) { d((T)target, (P1)p); };
}
private static Func<knownT, knownR> F0<T, knownT, knownR>(MethodInfo method)
where T : knownT
{
Func<T, knownR> d = (Func<T, knownR>)Delegate.CreateDelegate
(typeof(Func<T, knownR>), method);
return delegate(knownT target) { return d((T)target); };
}
private static Func<knownT, knownP1, knownR>
F1<T, P1, knownT, knownP1, knownR>(MethodInfo method)
where T : knownT
where P1 : knownP1
{
Func<T, P1, knownR> d = (Func<T, P1, knownR>)
Delegate.CreateDelegate(typeof(Func<T, P1, knownR>), method);
return delegate(knownT target, knownP1 p) { return d((T)target, (P1)p); };
}
public static T MakeCallDlg<T>(MethodInfo method)
{
string creatorName = (method.ReturnParameter.ParameterType ==
typeof(void) ? "A" : "F") +
method.GetParameters().Length.ToString();
List<Type> signature = new List<Type>();
signature.Add(method.DeclaringType);
foreach (ParameterInfo pi in method.GetParameters())
{
signature.Add(pi.ParameterType);
}
signature.AddRange(typeof(T).GetGenericArguments());
return (T)typeof(Helper).GetMethod(creatorName,
BindingFlags.NonPublic | BindingFlags.Static).MakeGenericMethod
(signature.ToArray()).Invoke(null, new object[] { method });
}
public static Func<K> MakeNewDlg<K>(Type t)
{
return Delegate.CreateDelegate(typeof(Func<K>),
typeof(Helper).GetMethod("createNewObj2",
BindingFlags.NonPublic | BindingFlags.Static).MakeGenericMethod
(new Type[] { t, typeof(K) })) as Func<K>;
}
private static K createNewObj2<T, K>() where T : K, new()
{
return new T();
}
public static Func<object> MakeNewDlg(Type t)
{
return Delegate.CreateDelegate(typeof(Func<object>),
typeof(Helper).GetMethod("createNewObj").MakeGenericMethod
(new Type[] { t })) as Func<object>;
}
private static object createNewObj<T>() where T : new()
{
return new T();
}
}
class Program
{
static void Main(string[] args)
{
Type unknownType = Type.GetType("plugin.p4sdfm2mlwd5");
Type unknownParamType = Type.GetType("plugin.l5fks9rffn33md");
MethodInfo test = unknownType.GetMethod("testFunction");
Func<knownType> unknownTypeCreator =
Helper.MakeNewDlg<knownType>(unknownType);
Func<knownParamType> unknownParamTypeCreator =
Helper.MakeNewDlg<knownParamType>(unknownParamType);
Func<knownType, knownParamType, knownParamType> methodCaller =
Helper.MakeCallDlg<Func<knownType, knownParamType,
knownParamType>>(test);
knownType instance = unknownTypeCreator();
knownParamType param = unknownParamTypeCreator();
knownParamType result = methodCaller(instance, param);
int times = 50000000;
#region direct call
int i = times;
plugin.p4sdfm2mlwd5 unknownInstance = new plugin.p4sdfm2mlwd5();
plugin.l5fks9rffn33md unknownParam = new plugin.l5fks9rffn33md();
DateTime start = DateTime.Now;
while (i-- > 0)
result = unknownInstance.testFunction(unknownParam);
Console.WriteLine("Direct call - {0}",
(DateTime.Now - start).TotalMilliseconds);
#endregion
#region my way
i = times;
start = DateTime.Now;
while (i-- > 0)
result = methodCaller(instance, param);
Console.WriteLine("My way - {0}",
(DateTime.Now - start).TotalMilliseconds);
#endregion
#region Invoke
i = times;
object[] paramArray = new object[] { param };
start = DateTime.Now;
while (i-- > 0)
result = (knownParamType)test.Invoke(instance, paramArray);
Console.WriteLine("Invoke - {0}", (DateTime.Now - start).TotalMilliseconds);
#endregion
Console.ReadKey();
}
}
History
- 27th February, 2009: Initial post