简体   繁体   中英

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

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 .

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; }
}

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.

If code will wish to perform different actions based whether or not a generic type meets certain constraints, especially if some of these actions can't even compile if the generic type doesn't, it may be helpful to use a pattern somewhat similar to the that used by classes like Comparer<T> and EqualityComparer<T> . The trick is to use a static class with a generic parameter T which holds a static delegate to a method which has a parameter of type T , and have static methods, each of which takes a possibly-constrained generic parameter of type U and has a signature that will be compatible with the aforementioned delegate when U matches T . The first time an attempt is made to use the static class or its delegate, the static constructor of the generic class can use Reflection to construct a delegate to the method which should be used for the type. Such a call will fail at run-time if one attempts to construct a delegate to a generic method in such fashion as to violate the generic constraints of thereof; since exceptioins in static constructors are very bad, one should make sure to construct one's delegates using valid functions. On the other hand, once the delegate has been constructed once, all future calls can be dispatched straight through that delegate, with no need for further Reflection or type checking.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM