Friday, July 31, 2009

.NET Reflection - Generic Method Type Inference of a Boxed Parameter

Before I begin- this is a fairly lengthy post and if you'd rather not read the whole thing and would just like to know how to pass a boxed object to a generic method and have the run-time properly infer the base type of that object for the method, jump to the bottom.

Today I was working on an ASP.NET MVC project I've been putting together for the past couple weeks and I realized that I have been duplicating a small piece of code over and over out of a falsely perceived necessity. After considering my options and coming up with a simple solution, I began to put it into place- only to quickly discover that there was no way to do what I wanted without some clever Reflection.

My project is set up such that all of my View pages inherit from a class I defined, MasterViewModel, which holds a few top level variables my Master Page requires for rendering. While all of my Views do inherit from MasterViewModel, the majority of them take a derived class, ContentViewPage<T>, which contains a simple generic property housing the Data (simple or complex) for that View. With this design, when I construct my View I always have the Master Page Model items included in the Controller result without having to explicitly add them in my Controller method. This is all well and good, but as you may imagine it became rather annoying to have to wrap the Model data I gave the View in a ContentViewModel<T> object at the end of every Controller call.

public ViewResult SayHello() {
 return View(new ContentViewModel<string>("Hello"));
}

So after a little digging around on Stack Overflow I decided to create myself a couple of helpers to assist in the creation of my ContentViewModel<T> object.

public static class ContentViewModel
{
    public static ContentViewModel<T> Create<T>(T data)
    {
        return new ContentViewModel<T>(data);
    }

    public static readonly MasterViewModel Empty = new MasterViewModel();
}

With this in hand I now need only write ContentViewModel.Create("Hello") to produce the same object I defined above. This seemed like a pretty decent solution at first, but I still wasn't particularly satisfied. Then it occurred to me that if I overrode the View() method of the Controller class and wrapped my objects in a ContentViewModel<T> before calling base.View() I wouldn't ever need to type ContentView.Create() again.

protected override ViewResult View(IView view, object model)
{
    if (model == null)
        return base.View(view, ContentViewModel.Empty);
    if (model is MasterViewModel)
        return base.View(view, model);
    else
        return base.View(view, ContentViewModel.Create(model));
}

protected override ViewResult View(string viewName, string masterName, object model)
{
    if (model == null)
        return base.View(viewName, masterName, ContentViewModel.Empty);
    else if (model is MasterViewModel)
        return base.View(viewName, masterName, model);
    else
        return base.View(viewName, masterName, ContentViewModel.Create(model));
}

Simple and effective, I thought.

Server Error in '/' Application.

The model item passed into the dictionary is of type 'MvcScratch.Controllers.HomeController+ContentViewModel`1[System.Object]' but this dictionary requires a model item of type 'MvcScratch.Controllers.HomeController+ContentViewModel`1[System.String]'.

Or not. This was all working swell before... what went wrong? Well, the View() method's model property is a non-generic Object, that's what went wrong. Usually that wouldn't matter because our View knows what type to cast the Model to based on the Inherits="" attribute of <%@ Page %> and therefore although we downcast our Model for the call to View() in the Controller, the View knows what to expect and upcasts it for us. Trouble is, ContentViewModel.Create() doesn't know that at run time and it only has the type of the parameter to go off of, which, no thanks to the View() method, is an object. Keep in mind, C# still knows the actual type of the object, but at run time ContentViewModel.Create() is unable to infer that until it is within the scope of the method- which is after T has been given a Type. Thus, instead of a ContentViewModel<string> being returned, we get a ContentViewModel<object>. To confirm this:

static void Main(string[] args)
{
    object str = "Hello World";
    Foo(str);
}

static void Foo<T>(T param)
{
    Console.WriteLine("typeof(T): {0}", typeof(T));
    Console.WriteLine("param.GetType(): {0}", param.GetType());
}
Which outputs:
typeof(T):              System.Object
   param.GetType():        System.String

Clearly,we can see that doesn't work quite how we'd like it to, but fear not, we're not out of luck just yet! Using a little bit of reflection we can fix this problem. The solution? Rather than calling ContentViewModel.Create() directly, and allowing the run-time to infer the the generic type T, we're going to invoke it through the class's Type definition and explictly define T's type.

typeof(ContentViewModel).GetMethod("Create").MakeGenericMethod(new Type[] { data.GetType() }).Invoke(null, new object[] { data });

BAM! If you examine this code for a moment or two you should be able to get the general idea. Using Reflection we're grabbing the Type of our method's container and then returning a MethodInfo for that method using its string representation. With that MethodInfo we are calling MethodInfo.MakeGenericMethod() which takes a array of parameter Types (in the order they're defined in the method signature) that will strongly type the method's generic parameters. So instead of T mapping to an 'object' it maps to whatever 'data.GetType()' returns- the actual parameter type. Finally, we invoke the method with a call to MethodInfo.Invoke() which receives an instance of the given Type (or null for a static method) and an Object array containing each parameter in order (or null for a parameterless method). Now if we replace the View() code from above with this call instead we can rest easy knowing that ContentViewModel<T>'s generic type is correctly instantiated.

It is worth noting that of course there is some extra overhead being created here because of our calls into System.Reflection and for that reason I am not entirely sure if this working solution is ideal- it's at least a little iffy and I'd be curious to know what the server performance looks like in a high-traffic environment where this reflection call is being run on every Controller request that doesn't explictly wrap its View() model parameter in a ContentViewModel<T> first, but maybe one of my readers can shine some light on that?

Here is a complete sample of this method invocation technique outside of my MVC project:

class Program 
{
    static void Main(string[] args)
    {
        object str = "Hello World";
        object num = 5;
        object obj = new object();

        Console.WriteLine("var\tvalue\t\tFoo() Type\tCallFoo() Type");
        Console.WriteLine("-------------------------------------------------------");
        Console.WriteLine("{0}\t{1}\t{2}\t{3}", "str", str, MyClass.Foo(str), MyClass.CallFoo(str));
        Console.WriteLine("{0}\t{1}\t\t{2}\t{3}", "num", num, MyClass.Foo(num), MyClass.CallFoo(num));
        Console.WriteLine("{0}\t{1}\t{2}\t{3}", "obj", obj, MyClass.Foo(obj), MyClass.CallFoo(obj));
    }
}

class MyClass 
{
    public static Type Foo<T>(T param)
    {
        return typeof(T);
    }

    public static Type CallFoo(object param)
    {
        return (Type)typeof(MyClass).GetMethod("Foo").MakeGenericMethod(new[] { param.GetType() }).Invoke(null, new[] { param });
    }
}

Which, outputs:

var     value           Foo() Type      CallFoo() Type
   -------------------------------------------------------
   str     Hello World     System.Object   System.String
   num     5               System.Object   System.Int32
   obj     System.Object   System.Object   System.Object

No comments: