3

这是我所拥有的(使用 Rhino Mocks,但这不是问题的核心):

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

是否可以更具体地设置类型参数Cache<T>?就像是:

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

这当然不会编译。但如果可能的话,我想使用 Rhino Mocks 生成的类型,这是IEntity.

4

3 回答 3

3

可以在运行时使用反射创建一个封闭的泛型类型。问题是您很可能不得不仅使用反射继续操作它,因为(鉴于它的类型在编译时未知)您不能将其键入为直接可用的东西。

例如,要创建“某物”列表:

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

这个例子说明了几个要点:

  1. 如果在编译时指定了“目标类型”,则无法执行此操作。换句话说,CreateList不能接受t作为泛型类型参数并且仍然允许相同的功能。
  2. 在最坏的情况下,不能将新实例键入为object. 在这里,我们知道我们将始终创建一个IList,所以事情会好一些。

我没有 Rhino Mocks 的经验,但在你的情况下,它会转化为:

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

...但只有在适当的GenerateStub过载可用时。

于 2012-07-11T08:28:16.663 回答
2

请注意,虽然提到了 RhinoMocks,但唯一具体的部分是GenerateStub围绕提供的类型创建具体实现;其余的与 RhinoMocks 无关。

泛型类型参数在编译时解析 - 如您所知;这不允许在没有反射的情况下将类型作为通用参数内联输入(即:)var list = new List<typeof(int)>();

但是,您可以使用反射创建泛型类型。从本质上讲,如果您可以通过以下方式获取动态代理的类型:

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

MockRepository有 aGenerateStub需要 aTypeobject[]参数,所以从上面继续:

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

不幸的是,该stubbed项目是 type object,但正如 Lucero 在评论中所说的那样,可以使用通用协方差来获得比 更有用的类型object。我在下面演示这一点。


根据与 Lucero 的有趣讨论,如果您定义一个新ICache<out T>接口来表示 Cache,则通用协方差将允许您将生成的代理转换为基本类型 (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; }
}
于 2012-07-11T08:24:42.357 回答
0

如果代码希望基于泛型类型是否满足某些约束来执行不同的操作,特别是如果其中一些操作在泛型类型不满足的情况下甚至无法编译,那么使用类似于Comparer<T>和之类的类使用的EqualityComparer<T>。诀窍是使用带有泛型参数的静态类,该类T将静态委托保存到具有类型参数的方法的静态委托T,并且具有静态方法,每个方法都采用可能受约束的泛型类型参数U并具有将U匹配时与上述委托兼容T. 第一次尝试使用静态类或其委托时,泛型类的静态构造函数可以使用反射构造一个委托给应该用于该类型的方法。如果有人试图以违反其通用约束的方式构造一个泛型方法的委托,这样的调用将在运行时失败;由于静态构造函数中的异常非常糟糕,因此应确保使用有效函数构造自己的委托。另一方面,一旦构造了一次委托,所有未来的调用都可以直接通过该委托进行分派,无需进一步的反射或类型检查。

于 2012-07-11T15:39:33.197 回答