Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Casting generic type instances created using Reflection

Tags:

c#

reflection

I'm creating instances of a generic type using reflection:

public interface IModelBuilder<TModel>
{
    TModel BuildModel();
}

public class MyModel
{
    public string Name { get; set; }
}

public class MyModelBuilder : IModelBuilder<MyModel>
{
    public MyModel BuildModel()
    {
        throw new NotImplementedException();
    }
}

At runtime all we know is the Type of model e.g. MyModel. I can find instances of the relevant model builder like so:

var modelBuilders = from t in Assembly.GetExecutingAssembly().GetTypes()
                from i in t.GetInterfaces()
                where i.IsGenericType
                        && i.GetGenericTypeDefinition() == typeof(IModelBuilder<>)
                        && i.GetGenericArguments()[0] == modelType
                select t;

var builder = Activator.CreateInstance(modelBuilders.First());

But I'm not sure how I can then cast the instance as IModelBuilder<TModel> so I can call and work with the result of BuildModel().

like image 615
Ben Foster Avatar asked Jun 27 '12 12:06

Ben Foster


1 Answers

Since modelType is just a Type instance, you can't do that automatically, since there is no non-generic API available. Various options:

1: use reflection, for example (untested)

object builder = Activator.CreateInstance(...);
var model=builder.GetType().GetMethod("BuildModel").Invoke(builder,null);

2: cheat with dynamic:

dynamic builder = Activator.CreateInstance(...);
var model = builder.BuildModel();

3: make a non-generic version of IModelBuilder, and use that

Note that 1 & 2 rely on a public implementation of the interface, and will fail for a (perfectly legal) explicit interface implementation. For "1", you can fix this via:

var model = typeof(IModelBuilder<>).MakeGenericType(modelType)
       .GetMethod("BuildModel").Invoke(builder);

A final sneaky option is to flip from a non-generic method into a generic method, so inside the generic method you can use all the members directly. There's a lazy way to do that via dynamic:

interface ISneaky<T>
{
    T Foo { get; }
}
class Sneaky<T> : ISneaky<T>
{
    T ISneaky<T>.Foo { get { return default(T); } }
}
class Program
{
    static void Main()
    {
        Execute(typeof(int));
    }
    static void Execute(Type t)
    {
        dynamic obj = Activator.CreateInstance(
            typeof(Sneaky<>).MakeGenericType(t));
        // crafy hack to flip from non-generic code into generic code:
        Evil(obj);
    }
    static void Evil<T>(ISneaky<T> sneaky)
    {   // in here, life is simple; no more reflection
        Console.WriteLine("{0}: {1}", typeof(T).Name, sneaky.Foo);
    }
}
like image 143
Marc Gravell Avatar answered Nov 05 '22 05:11

Marc Gravell