Introduction
A few days ago, I needed to dynamically invoke a method on a class. Saying only this, things appear very simple.
As usual it got complicated... I figured out that the method was a generic one, and that the class had several overloads defined for the given method.
Background
When embracing this new task, I was obviously concerned with the performance output of the solution, and that's why I opted for the DynamicMethod
class (new in .NET Framework 2.0) and Reflection Emit.
There are several pages on the Web that compare the DynamicMethod
approach with the Reflection Invoke one, with a very large performance boost for the first one (please read this MSDN magazine article).
To use the DynamicMethod
class approach, one must be able to generate the IL code for the method. I have some knowledge on this subject, but you all can do it. Simply code your method in C# or another .NET language, compile it, and then use Lutz Roeder's .NET Reflector to look at the generated IL. With a little persistence you will get the job done.
The Solution
The development output is a simple static class with a few public methods that allow you to create a GenericInvoker
for any generic method.
GenericInvoker
is a delegate defined as follows.
Note: On methods that have no return value, a call to the delegate will always return null
.
public delegate object GenericInvoker(object target, params object[] arguments);
You can create an instance of the GenericInvoker
delegate by simply calling one of the overrides for the GenericMethodInvokerMethod
method on the DynamiMethods
static class (included in the article source code archive).
Note: The GenericInvoker
delegate creation can be a slow process. Therefore if you are going to use it in some kind of loop, you should consider caching the value to reduce the performance impact.
Using the Code
Here is a simple example on how to use the supplied DynamicMethods
class:
public class SampleClass {
private string instanceName;
public SampleClass(string instanceName) {
this.instanceName = instanceName;
}
public void Test<TType>(TType s) {
MessageBox.Show(string.Format("{0} From {1}", s, this.instanceName));
}
public string Concatenate<TType>(TType lhs, TType rhs) {
return string.Format("{0}{1}", lhs, rhs);
}
public string Concatenate<TType>(string prefix, TType lhs, TType rhs) {
return string.Format("{0} - {1}{2}", prefix, lhs, rhs);
}
}
public class Tests {
public void Tests() {
SampleClass instance = new SampleClass("Instance 1");
GenericInvoker invoker;
invoker = DynamicMethods.GenericMethodInvokerMethod(typeof(SampleClass), "Test",
new Type[] { typeof(string) });
ShowResult(invoker(instance, "this is a tests"));
invoker = DynamicMethods.GenericMethodInvokerMethod(typeof(SampleClass),
"Concatenate", new Type[] { typeof(int) },
new Type[] { typeof(int), typeof(int) });
ShowResult(invoker(instance, 100, 200));
invoker = DynamicMethods.GenericMethodInvokerMethod(typeof(SampleClass),
"Concatenate", new Type[] { typeof(int) },
new Type[] { typeof(string), typeof(int), typeof(int) });
ShowResult(invoker(instance, "PREFIX", 100, 200));
}
private static void ShowResult(object result) {
if (null == result) {
MessageBox.Show("return is null");
} else {
MessageBox.Show(string.Format("return is {0}", result));
}
}
}
Points of Interest
One of the major problems that I faced was related to the GetMethod
method from the Type
class.
Although GetMethod
works very well with normal type methods, it doesn't do so for generic methods. If the generic method has overloads, than the GetMethod
call will always return null
.
To overcome this limitation, I had to use the GetMethods
method and iterate through all the type methods to get the correct one. Here is the code that gets the job done:
private static void FindMethod(Type type, string methodName, Type[] typeArguments,
Type[] parameterTypes, out MethodInfo methodInfo,
out ParameterInfo[] parameters) {
methodInfo = null;
parameters = null;
if (null == parameterTypes) {
methodInfo = type.GetMethod(methodName, BindingFlags.Public | BindingFlags.Instance);
methodInfo = methodInfo.MakeGenericMethod(typeArguments);
parameters = methodInfo.GetParameters();
} else {
MethodInfo[] methods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance);
foreach (MethodInfo method in methods) {
if (method.Name == methodName) {
MethodInfo genericMethod = method.MakeGenericMethod(typeArguments);
parameters = genericMethod.GetParameters();
if (parameters.Length == parameterTypes.Length) {
for (int i = 0; i < parameters.Length; i++) {
if (parameters[i].ParameterType != parameterTypes[i]) {
continue;
}
}
methodInfo = genericMethod;
break;
}
}
}
if (null == methodInfo) {
throw new InvalidOperationException("Method not found");
}
}
}
History
- 2007.01.02: Initial release