Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Dynamically Invoke Generic Methods

5.00/5 (19 votes)
1 Jan 2008GPL32 min read 2   1K  
Reflection on generics can be complicated. This article shows how to use the DynamicMethod class to dynamically invoke a generic method.

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.

C#
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:

C#
// sample 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);
  }
}

// Tests class
public class Tests {
  public void Tests() {

    SampleClass instance = new SampleClass("Instance 1");
    GenericInvoker invoker;

    // invoke method that returns void
    invoker = DynamicMethods.GenericMethodInvokerMethod(typeof(SampleClass), "Test", 
        new Type[] { typeof(string) });
    ShowResult(invoker(instance, "this is a tests"));

    // invoke method that returns string, the parameter types are used to find 
    // the correct overload
    invoker = DynamicMethods.GenericMethodInvokerMethod(typeof(SampleClass), 
        "Concatenate", new Type[] { typeof(int) },
      new Type[] { typeof(int), typeof(int) });
    ShowResult(invoker(instance, 100, 200));

    // invoke method that returns string, the parameter types are used to find 
    // the correct overload
    invoker = DynamicMethods.GenericMethodInvokerMethod(typeof(SampleClass), 
        "Concatenate", new Type[] { typeof(int) },
      new Type[] { typeof(string), typeof(int), typeof(int) });
    ShowResult(invoker(instance, "PREFIX", 100, 200));
  }

  // show GenericInvoker result
  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:

C#
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 {
     // Method is probably overloaded. As far as I know there's no other way 
     // to get the MethodInfo instance, we have to
     // search for it in all the type methods
    MethodInfo[] methods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance);
    foreach (MethodInfo method in methods) {
      if (method.Name == methodName) {
         // create the generic method
        MethodInfo genericMethod = method.MakeGenericMethod(typeArguments);
        parameters = genericMethod.GetParameters();

         // compare the method parameters
        if (parameters.Length == parameterTypes.Length) {
          for (int i = 0; i < parameters.Length; i++) {
            if (parameters[i].ParameterType != parameterTypes[i]) {
              continue; // this is not the method we're looking for
            }
          }

           // if we're here, we got the right method
          methodInfo = genericMethod;
          break;
        }
      }
    }

    if (null == methodInfo) {
      throw new InvalidOperationException("Method not found");
    }
  }
}

History

  • 2007.01.02: Initial release

License

This article, along with any associated source code and files, is licensed under The GNU General Public License (GPLv3)