使用基类作为IEnumerable<T>的泛型

4
我对面向对象编程(OOP)有很好的理解,包括继承和多态、接口等。我遇到了一个奇怪的情况,不明白为什么它根本不起作用...编辑:好吧,我发现协变(或逆变?)可能会解决这个问题,但关键是我们仍然在使用.NET 2.0。我如何在不升级到C# 4.0的情况下解决这个问题?这就是情况。给定这两个类:
public class CustomCollectionType<T> : IEnumerable<T>
{
    /* Implementation here, not really important */
}

public class Entity : EntityBase
{
    /* Implentation here, not important */
}

编译器在我尝试使用这个通用方法时发出了警告。
public void LoopThrough(IEnumerable<EntityBase> entityList)
{
    foreach(EntityBase entity in entityList) 
    {
        DoSomething(entity);  
    }
}

尝试按照以下方式使用它:
CustomCollectionType<Entity> entityList;
/* Add items to list */

LoopThrough(entityList);

错误提示说我无法将CustomCollectionType<Entity>转换为IEnumerable<EntityBase>

然而,我可以这样做:

public void Foo(EntityBase entity)
{
    entity.DoSomething();
}

Foo(new Entity());

And this :

public void Bar(IEnumerable<Entity> entityList)
{ ... }

CustomCollectionType<Entity> entityList;

Bar(entityList);

为什么我不能在层级结构中的最高类中创建我的方法?这些类型显然是兼容的... 我有什么遗漏吗?
编辑:我想在不以任何方式更改现有类的情况下解决此问题,因此在任何类中创建新方法或实现其他接口都是不可能的。

Lippert有一系列长篇文章介绍这个主题。第一部分可以在http://blogs.msdn.com/b/ericlippert/archive/2007/10/16/covariance-and-contravariance-in-c-part-one.aspx找到。 - Rune FS
回答:在Dieter和Dieter Java编程系列中。 - Code Droid
4个回答

15

让我们考虑第一个情况。您有:

class Bowl<T> : IEnumerable<T> {}
class Apple : Fruit {}
...
void LoopThrough(IEnumerable<Fruit> fruits) ...

并且你调用

Bowl<Apple> apples = whatever;
LoopThrough(apples);

在C# 3.0中会失败;在C# 4.0中会成功,因为IEnumerable<T>现在支持对T进行协变;一系列苹果可以被视为一系列水果。

要使它在C# 3.0中工作,您可以使用Cast序列运算符。

Bowl<Apple> apples = whatever;
LoopThrough(apples.Cast<Fruit>());

要在C# 2.0中使其正常工作,需要自己实现Cast序列运算符。这只需要几行代码。

请注意,在C# 4.0中仍然不合法的是:

Bowl<Fruit> fruits = new Bowl<Apples>();

当然你可以这样说:

fruits.Add(new Orange());

你刚刚把一个橙子放进了只能装苹果的碗里。


4

是的,.NET在这方面可能有点麻烦,因为它无法一次性转换您所有的通用参数。相反,可以尝试使用如下的通用方法来缓解问题。

public void LoopThrough<T>(IEnumerable<T> entityList) where T : EntityBase
{
    foreach(T entity in entityList) 
    {
        DoSomething(entity as EntityBase);  
    }
}

0

这些类型是兼容的,但有点不兼容,主要原因是您在参数中使用了基本类型IEnumerable而不是实际类型,尽管Entity的基类是entitybase,因为类型参数和约束的规则对于泛型类的行为有几个影响,特别是关于继承和成员可访问性方面。

泛型类是不变的。换句话说,如果输入参数指定了List<BaseClass>,如果您尝试提供List<DerivedClass>,则会在编译时出现错误。

这就是为什么您会得到那个错误,而在您的最后一个示例中,T是相同的。

然而,如果您使用接口,它将完全正常工作,因为所有接口都是兼容的。

  public class Entity : IEntityBase  
  {      /* Implentation here, not important */  }  

public void LoopThrough(IEnumerable<IEntityBase> entityList)  
 {      foreach(IEntityBase entity in entityList)       
     {          DoSomething(entity);        }  }  

然后你的方法就可以正常工作了

CustomCollectionType<Entity> entityList;      LoopThrough(entityList);  

因为entitylist具有IEntityBase类型

你可以尝试的另一件事是使用typeof(获取类型)或使用强制转换,它应该可以工作


0
我可能有所遗漏,但是如果您的CustomCollectionType意图应该是基于实体的,但又允许使用IEnumerable,那么您不应该首先将IT作为Entity base的基础吗?例如...
public class CustomCollectionType<T> : EntityBase, IEnumerable<T>
{
    /* Implementation here, not really important */
}

那么你的LoopThrough应该可以工作,因为自定义集合类型是从EntityBase派生的,并且具有任何预期的方法、属性等可用...或者最坏的情况下,在调用函数时你可以进行类型转换,例如

Bowl<Apple> apples = whatever;
LoopThrough((EntityBase)apples);

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