Object-Oriented Programming

System.Reflection – Generic Methods in C# Using Reflection

Posted on Updated on

Generics came on the scene in .NET 2.0 and have been a mainstay ever since! I have developed numerous application frameworks over the years and in every case I have made full use of generics. Very similar to templates in C++, generics allow dynamic type-based operations in a strongly-typed manner and can provide build-time validation. Generics allow us define classes and methods without committing to specific types while we are coding. I am not going to go into a discussion of generics in a general sense since this MSDN resource does a pretty good job of providing a description.

Generic Methods

For our example on to how invoke generic methods via Reflection, we are first going to create a few simple classes whose actual function is a bit nonsensical, but I believe the best way to explain intermediate to advanced concepts is to use simple examples, and thus this is the theme of a large part of my blog :).

  1. To start, using Visual Studio let’s create a new solution that we will call GenericMethodWalkthroughSolution.
  2. Next, let’s add two projects to our solution: a C# Class Library which we will call MyClassLibrary and a C# Console Application which we will call MyConsoleApplication.
  3. In the Console application, add a project reference to MyClassLibrary.
  4. Right-click the MyConsoleApplication project and selected “Set as Startup Project”.

When we are done with these four steps, our Solution Explorer should look like this:

Solution Explorer View with both projects and a project reference added
Solution Explorer View with both projects and a project reference added

Next, we will add four classes to our Class Library project. For fun, I am using classes that you may remember from your very first introduction to Object-Oriented Programming (OOP). Don’t laugh though as they are perfect for this discussion!

The classes are outlined below:

  • Mammal.cs – a class that will be the base class for the remaining two classes.
  • Human.cs – a class that will be instantiated to represent a human.
  • Dog.cs – a class that will be instantiated to represent a dog
  • Manager.cs – a class that will contain a generic method that will create an instance of a human or a dog, generically and a couple of methods that create either specifically.

We will just add a couple of properties to each of the first three then a single generic method to the fourth.

Since the Mammal class is our base class, let’s define a couple of properties that are common to all mammals. How about Weight and Age? Sounds good to me.

Our Mammal class looks like this:

public class Mammal
{
    public Int32 Weight
    {
        get;
        set;
    }

    public Int32 Age
    {
        get;
        set;
    }
}

Our Human class has a couple of properties and inherits Mammal:

public class Human : Mammal
{
    public string Name
    {
        get;
        set;
    }

    public String Occupation
    {
        get;
        set;
    }
}

Our Dog class has a couple of properties and inherits Mammal as well:

public class Dog : Mammal
{
    public String Breed
    {
        get;
        set;
    }

    public Boolean IsLongHaired
    {
        get;
        set;
    }
}

Finally, our Manager class has a single generic method called Create that is generic and accepts any type that inherits Mammal. See the simple code below:

public class Manager
{
    public T CreateMammal<T>()
        where T : Mammal, new()
    {
        return new T();
    }

    public Human CreateHuman()
    {
        return new Human();
    }

    public Dog CreateDog()
    {
        return new Dog();
    }
}

These methods are quite simple, so let’s not focus on their simplicity but focus instead on the concept we’re discussing. The CreateHuman() and CreateDog() methods simply return a new instance of each type. The Create method does the same, but generically and we will move ahead with our examples of how to call generic methods via Reflection.

Let’s return to our Console Application’s Program class and write some code in our static Main() method as shown below.

static void Main(string[] args)
{
    //first, let's just call the methods directly
    Manager manager = new Manager();

    //create a Human
    Human firstHuman = manager.CreateHuman();

    //create a Dog
    Dog firstDog = manager.CreateDog();

    //create a human generically
    Human secondHuman = manager.CreateMammal<Human>();

    //create a dog generically
    Dog secondDog = manager.CreateMammal<Dog>();
}

I included this initial code simply to illustrate normal invocation of each of the methods. Now we are going to invoke each of the methods via Reflection in the code block below.

static void Main(string[] args)
{
    Manager manager = new Manager();
    Type managerType = manager.GetType();

    //invoke the CreateHuman method
    Human human = (Human)managerType.GetMethod("CreateHuman").Invoke(manager, null);

    //invoke the CreateDog method
    Dog dog = (Dog)managerType.GetMethod("CreateDog").Invoke(manager, null);

    //invoke the CreateMammal method and create a Human
    MethodInfo createMammalMethod = managerType.GetMethod("CreateMammal");
    MethodInfo genericCreateMammalMethod = createMammalMethod.MakeGenericMethod(new Type[] { typeof(Human) });
    Human genericHuman = (Human)genericCreateMammalMethod.Invoke(manager, null);

    //invoke the CreateMammal method and create a Dog
    MethodInfo createMammalMethodDog = managerType.GetMethod("CreateMammal");
    MethodInfo genericCreateMammalMethodDog = createMammalMethodDog.MakeGenericMethod(new Type[] { typeof(Dog) });
    Dog genericDog = (Dog)genericCreateMammalMethodDog.Invoke(manager, null);
}

Okay, the last generic method invocations require some discussion. Let’s break out the first generic invocation of the CreateMammal method in the code block below and insert some commentary to provide an explanation.

//invoke the CreateMammal method and create a Human
//As with the other non-generic, Reflection-based invocations in the first two
//methods, we first have to get a reference to the MethodInfo object for the CreateMammal method.
MethodInfo createMammalMethod = managerType.GetMethod("CreateMammal");

//When invoking a generic method, we have to call the MakeGenericMethod method to tell the runtime
//what generic arguments are involved. In this case, we have one argument whose type is Human.
//Therefore, we create a new MethodInfo object that contains the substituted types for the generic argument(s).
MethodInfo genericCreateMammalMethod = createMammalMethod.MakeGenericMethod(new Type[] { typeof(Human) });

//Instead of using the first MethodInfo object we actually invoke the second (generic) one.
Human genericHuman = (Human)genericCreateMammalMethod.Invoke(manager, null);

So the important point here is this: to invoke a generic method via Reflection, you must call the MakeGenericMethod method of the MethodInfo object that holds a reference to the method prior to substitution of the generic arguments. Remember that a generic method may have more than one generic type, so therefore the MakeGenericMethod method accepts an object array that is the collection of generic types.

Now, if we don’t know the generic types at runtime like we did in this example, we can get them through the same MethodInfo object!  See the code block below:

MethodInfo method = managerType.GetMethod("CreateMammal");
if (method.IsGenericMethod == true)
{
    Type[] genericArguments = method.GetGenericArguments();
}

If we place a breakpoint on the fourth line of this block and add a Watch to the genericArguments object we can see for ourselves that the generic type is a type that has a BaseType of Mammal. See below:

Generic Argument Display in Watch Window
Generic Argument Display in Watch Window

 

Related Posts

System.Reflection – Working with the Assembly Class

System.Reflection – Working with the Type Class

Advertisement