Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In C#, is it possible to determine the type parameter of a generic at runtime?

Tags:

c#

.net

generics

Here's sort of what I have (using Rhino Mocks, but that isn't central to the question):

var entityMock = MockRepository.GenerateMock<IEntity>();
this.Cache = MockRepository.GenerateStub<Cache<IEntity>>();

Is it possible to be more specific in setting the type parameter of Cache<T>? Something like:

var entityMock = MockRepository.GenerateMock<IEntity>();
this.Cache = MockRepository.GenerateStub<Cache<typeof(entityMock)>>();

This doesn't compile of course. But I would like to, if possible, use the type that Rhino Mocks generates, which is a concrete implementation of IEntity.

like image 266
Peter Avatar asked Sep 16 '25 12:09

Peter


2 Answers

You can create a closed generic type at runtime using reflection. The problem is that you will most likely have to continue operating it using just reflection because (given that its type is not known at compile time) you cannot type it as something usable directly.

For example, to create a list of "something":

public IList CreateList(Type t)
{
    var openListType = typeof(List<>);
    return (IList)openListType.MakeGenericType(t);
}

This example illustrates a couple of important points:

  1. You cannot do this if the "target type" is specified at compilation. In other words, CreateList cannot accept t as a generic type parameter and still allow the same functionality.
  2. In the worst case scenario the new instance cannot be typed as anything else than object. Here we know that we will always create an IList, so things are a bit better.

I don't have experience with Rhino Mocks, but in your case it would translate to this:

var entityMock = MockRepository.GenerateMock<IEntity>()
var cacheType = typeof(Cache<>).MakeGenericType(entityMock.GetType());
this.Cache = MockRepository.GenerateStub(cacheType);

...but only if an appropriate GenerateStub overload is available.

like image 57
Jon Avatar answered Sep 18 '25 10:09

Jon


Note that although RhinoMocks is mentioned, the only specific part is GenerateStub which creates a concrete implementation around the provided type; the rest is agnostic of RhinoMocks.

Generic type arguments are resolved at compile time - as you know; this disallows entering the type as a generic argument in-line without reflection (ie: var list = new List<typeof(int)>();).

However, you can create generic types using reflection. In essense, if you can get the type of the dynamic proxy from something like:

var entityMock = MockRepository.GenerateMock<IEntity>();
var dynamicType = entityMock.GetType();

MockRepository has a GenerateStub that takes a Type and object[] arguments, so carrying on from above:

var cacheType = typeof(Cache<>);
var genericType = cacheType.MakeGenericType(dynamicType);
var stubbed = MockRepository.GenerateStub(genericType, null);

The stubbed item is unfortunately of type object, but as Lucero states in the comments, it would be possible to use generic co-variance to gain a type more usable than just object. I demonstrate this below.


Based on an interesting discussion with Lucero, if you define a new ICache<out T> interface to represent the Cache, the generic co-variance will then allow you to cast the resulting proxies to the base type (of ICache<IEntity>):
class Program
{
    static void Main(string[] args)
    {
        // The concrete entity.
        IEntity entityMock = MockRepository.GenerateMock<IEntity>();
        entityMock.Stub(s => s.Name).Return("Adam");

        // The runtime type of the entity, this'll be typeof(a RhinoMocks proxy).
        Type dynamicType = entityMock.GetType();

        // Our open generic type.
        Type cacheType = typeof(ICache<>);

        // Get the generic type of ICache<DynamicProxyForIEntity> (our closed generic type).
        Type  genericType = cacheType.MakeGenericType(dynamicType);

        // Concrete instance of ICache<DynamicProxyForIEntity>.
        object stubbed = MockRepository.GenerateStub(genericType, null);

        // Because of the generic co-variance in ICache<out T>, we can cast our
        // dynamic concrete implementation down to a base representation
        // (hint: try removing <out T> for <T> and it will compile, but not run).
        ICache<IEntity> typedStub = (ICache<IEntity>)stubbed;

        // Stub our interface with our concrete entity.
        typedStub.Stub(s => s.Item).Return(entityMock);

        Console.WriteLine(typedStub.Item.Name); // Prints "Adam".
        Console.ReadLine();
    }
}

public interface ICache<out T>
{
    T Item { get; }
}

public interface IEntity
{
    string Name { get; }
}
like image 34
12 revsAdam Houldsworth Avatar answered Sep 18 '25 09:09

12 revsAdam Houldsworth