Larry Steinle

January 3, 2015

Executing Generics with Dynamic Parameter Types

Filed under: C,VS.Net — Larry Steinle @ 8:22 am
Tags: , ,

Today we will learn how to instantiate a generic class and execute a generic method without knowing what class type to use when instantiating the generic (i.e. new GenericClass<DynamicParameterType>();).

The Challange: Why I Needed a Dynamic Generic Parameter Type

While working with the System.Windows.Input.ICommand interface in a Windows Presentation Foundation (WPF) project I encountered the scenario where the value passed into the parameter argument of the ICommand.Execute method needed to be used in a generic library class.

public class DoSomethingCommand : ICommand
{
  public event EventHandler CanExecuteChanged;

  public bool CanExecute(object parameter)
  { return true; }

  public void Execute(object parameter)
  {
    var doSomething = new GenericClass<DataModel>();
    doSomething.Execute(parameter as DataModel);
  }
}

I wanted the command to be usable with multiple class types. But generics require that the class type be known up front. In the example above I specify the <DataModel> class for the generic parameter type. But what if the parameter isn’t of type DataModel and I still need to use the parameter in the generic method?

What I needed was the ability to do something like this:

public class DoSomethingCommand : ICommand
{
  public event EventHandler CanExecuteChanged;

  public bool CanExecute(object parameter)
  { return true; }

  public void Execute(object parameter)
  {
    var doSomething = new GenericClass<parameter.getType()>();
    doSomething.Execute(parameter);
  }
}

Unfortunately the previous code block won’t work. You can’t pass a type as a generic parameter type.

The Solution: Execute Generics Using Reflection

The only way to support a dynamic parameter type in a generic class (or method) is with reflection. So I created a set of methods that would allow me to dynamically instantiate a generic class and execute a generic method. This way I could bypass the need to define up front the class type to use in the generic method.

For further code reuse I created the methods so that they would support both standard and generic scenarios. This way I could execute any class dynamically using reflection as needed.

So what I will end up with is the following code that will support dynamic parameters in generics:

public class DoSomethingCommand : ICommand
{
  public event EventHandler CanExecuteChanged;

  public bool CanExecute(object parameter)
  { return true; }

  public void Execute(object parameter)
  {
    DynamicGeneric.Execute(typeof(GenericClass<>), parameter.GetType(), "Execute", parameter);
  }
}

The Sample Code

I began by creating the following sample classes that I will execute using reflection to demonstrate the ability to call a generic method using a dynamic parameter type.

using System.Diagnostics;

public class DiagnosticWriter
{
  public void WriteLine(string data)
  {
    Debug.WriteLine("DiagnosticWriter.WriteLine: " + data);
  }

  public void WriteLineGenericMethod<T>(T data)
  {
    Debug.WriteLine("DiagnosticWriter.WriteLineGenericMethod<T>: " + data);
  }
}

public class GenericDiagnosticWriter<T>
{
  public void WriteLine(string data)
  {
    Debug.WriteLine("GenericDiagnosticWriter<T>.WriteLine: " + data);
  }

  public void WriteLineGeneric(T data)
  {
    Debug.WriteLine("GenericDiagnosticWriter<T>.WriteLineGeneric(T): " + data);
  }

  public void WriteLineGenericMethod<S>(S data)
  {
    Debug.WriteLine("GenericDiagnosticWriter<T>.WriteLineGenericMethod<S>: " + data);
  }
}

Using Reflection to Instantiate Standard and Generic Class Types

To instantiate a class using reflection is fairly simple with the Activator.CreateInstance method:

var classInstance = Activator.CreateInstance(typeof(DiagnosticWriter));

To instantiate a generic class requires a bit more effort (but not much):

var classType = typeof(GenericDiagnosticWriter<>);
classType = classType.MakeGenericType(typeof(int));
var classInstance = Activator.CreateInstance(classType);

The above code would normally be instantiated with a static parameter type as follows:

var classInstance = new GenericDiagnosticWriter<int>();

Using Reflection to Execute Standard and Generic Methods

To execute a standard method is very simple as well:

var classMethod = classInstance.GetType().GetMethod("WriteLine");
classMethod.Invoke(classInstance, "abc");

Again, a generic method requires a bit more work:

var classMethod = classInstance.GetType().GetMethod("WriteLineGenericMethod");
var genericMethod = classMethod.MakeGenericMethod(typeof(int));
genericMethod.Invoke(classInstance, 123);

The above method would normally be executed with a static parameter type as follows:

classInstance.WriteLineGenericMethod<int>(123);

Putting It All Together

We can test if a class is a generic using the IsGenericType method and we can test if a method is a generic method using the IsGenericMethod method. With these two properties we can create a single method that can handle all the various standard and generic scenarios we just covered!

public partial class DynamicExecutor
{
  public static void Execute(Type classType, Type classParameterType, string methodName, Type methodParameterType, object parameter)
  {
    //Create class instance or generic class instance as appropriate
    var classObj = classType.IsGenericType ? classType.MakeGenericType(classParameterType) : classType;
    var classInstance = Activator.CreateInstance(classObj);

    //Create method reference or generic method reference as appropriate
    var classMethod = classInstance.GetType().GetMethod(methodName);
    if (classMethod.IsGenericMethod)
      classMethod = classMethod.MakeGenericMethod(methodParameterType);

    //Execute the method
    classMethod.Invoke(classInstance, new object[] { parameter });
  }
}

To further simplify things we will add a few overloading methods:

public partial class DynamicExecutor
{
  public static void Execute(Type classType, string methodName, object parameter)
  { Execute(classType, null, methodName, null, parameter); }

  public static void Execute(Type classType, Type classParameterType, string methodName, object parameter)
  { Execute(classType, classParameterType, methodName, null, parameter); }

  public static void Execute(Type classType, string methodName, Type methodParameterType, object parameter)
  { Execute(classType, null, methodName, methodParameterType, parameter); }
}

Demo

The following sample code demonstrates that the DynamicExecutor.Execute method works and that we can support dynamic parameter types in our generics!

//Execute normal method inside normal class (DiagnosticWriter.WriteLine)
DynamicGeneric.Execute(typeof(DiagnosticWriter), "WriteLine", "abc");

//Execute generic method inside normal class (DiagnosticWriter.WriteLineGenericMethod)
DynamicGeneric.Execute(typeof(DiagnosticWriter), "WriteLineGenericMethod", typeof(int), 345);

//Execute normal method inside generic class (GenericDiagnosticWriter.WriteLine)
DynamicGeneric.Execute(typeof(GenericDiagnosticWriter<>), typeof(int), "WriteLine", "abc");

//Execute generic method inside generic class (GenericDiagnosticWriter.WriteLineGeneric)
DynamicGeneric.Execute(typeof(GenericDiagnosticWriter<>), typeof(int), "WriteLineGeneric", 123);

//Execute generic method inside generic class (GenericDiagnosticWriter.WriteLineGenericMethod)
DynamicGeneric.Execute(typeof(GenericDiagnosticWriter<>), typeof(int), "WriteLineGenericMethod", typeof(double), 123.321);

You can add support for even more reflection scenarios by creating new overloaded methods to handle multiple generic parameter types and multiple parameters for the method.

Summary

Now that wasn’t so bad! Supporting generic with dynamic parameter types is actually a fairly straight forward task when using reflection.

Advertisements

Leave a Comment »

No comments yet.

RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Create a free website or blog at WordPress.com.

%d bloggers like this: