在C#中,是否可以在运行时确定泛型的类型参数?

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的具体实现。

Peter,请重新考虑已接受的答案;Adam的回答现在更加完整和切题。 - Lucero
哇,这些答案比我预期的更加强大。有点超出我的理解范围!我会重新考虑你的答案Lucero,但你得给我一些时间来实验。 - Peter
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重载可用时才能这样做。


2
请注意,尽管提到了RhinoMocks,但唯一具体的部分是GenerateStub,该部分在提供的类型周围创建具体实现;其余部分与RhinoMocks无关。
通常情况下,泛型类型参数是在编译时解析的,这意味着您无法在不使用反射的情况下以内联方式输入类型作为泛型参数(例如:var list = new List<typeof(int)>();)。
然而,您可以使用反射来创建泛型类型。实际上,如果您可以从以下内容中获取动态代理的类型:
var entityMock = MockRepository.GenerateMock<IEntity>();
var dynamicType = entityMock.GetType();

MockRepository有一个GenerateStub方法,它需要一个Typeobject[]作为参数。继续上面的内容:

var cacheType = typeof(Cache<>);
var genericType = cacheType.MakeGenericType(dynamicType);
var stubbed = MockRepository.GenerateStub(genericType, null);
stubbed项目的类型不幸是object,但正如Lucero在评论中所述,可以使用通用型协变来获得比object 更可用的类型。我在下面演示了这一点。
基于与Lucero的有趣讨论,如果您定义一个新的ICache<out T>接口来表示缓存,通用型协变将允许您将结果代理转换为基本类型(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; }
}

1
这不是正确的。您可以在运行时创建具有即时决定类型参数的封闭泛型类型。 - Jon
1
踩票是我的。结合Jon的代码和使用泛型协变性的接口(例如IEnumerable<>),您可以在运行时使用比声明更具体的实现(例如,您具有类型为IEnumerable<object>的变量,并且通过MakeGenericType和Activator反射创建了List<Foo>,然后您可以将其分配给此变量而无需回退到object或dynamic)。因此,说您永远无法做到这一点是不正确的。 - Lucero
1
@AdamHouldsworth,如果RhinoMocks没有为其模拟提供协变接口,那么这可能是正确的。然而,OP明确表示:“使用Rhino Mocks,但这并不是问题的核心”,因此指出确实可能存在这种情况比说不行更好... - Lucero
@Lucero,我已经采纳了你的想法并更新了我的答案,希望它能够符合要求。最终结果是ICache针对IEntity的动态代理进行了实现。 - Adam Houldsworth
1
@AdamHouldsworth,是的,这正是我所想的。干得好! - Lucero
显示剩余7条评论

0
如果代码想要根据泛型类型是否符合某些约束条件执行不同的操作,特别是如果其中一些操作甚至在泛型类型不符合时都无法编译,那么使用类似于`Comparer<T>`和`EqualityComparer<T>`这样的类所使用的模式可能会有帮助。技巧是使用一个具有泛型参数`T`的静态类,它保存了对具有参数类型为`T`的方法的静态委托,并且具有静态方法,每个静态方法都需要一个可能受限制的泛型参数类型`U`,并且具有与前述委托兼容的签名,当`U`匹配`T`时。第一次尝试使用静态类或其委托时,通用类的静态构造函数可以使用反射来构造应该用于该类型的方法的委托。如果尝试以这种方式构造泛型方法的委托以违反其泛型约束,则此类调用将在运行时失败;由于静态构造函数中的异常非常糟糕,因此应确保使用有效函数构造委托。另一方面,一旦委托被构造一次,所有未来的调用都可以直接通过该委托分派,而无需进一步进行反射或类型检查。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接