c#中的协方差

时间:2010-10-27 22:09:28

标签: c# casting covariance

是否可以在C#4.0中投放List<Subclass>List<Superclass>

这些方面的东西:

class joe : human {}

List<joe> joes = GetJoes();

List<human> humanJoes = joes;

这不是协方差的意思吗?

如果可以的话:

human h = joe1 as human;

为什么你不能做到

List<human> humans = joes as List<human>; 

比做人([joe]人类[0]不合法,因为该项目已被降级......每个人都会很开心。现在唯一的选择是创建一个新的列表

5 个答案:

答案 0 :(得分:26)

你不能这样做,因为它不安全。考虑:

List<Joe> joes = GetJoes();    
List<Human> humanJoes = joes;
humanJoes.Clear();
humanJoes.Add(new Fred());
Joe joe = joes[0];

显然,最后一行(如果不是较早的那行)失败 - 因为Fred不是JoeList<T>的不变性可以防止编译时的错误,而不是执行时间。

答案 1 :(得分:6)

实例化一个以joes为输入的新人员列表:

List<human> humanJoes = new List<human>(joes);

答案 2 :(得分:2)

没有。 C#4.0的协/反变量功能仅支持接口和委托。不支持List<T>等具体类型。

答案 3 :(得分:1)

没有。正如Jared所说,C#4.0的协同/反向变换功能仅支持接口和委托。但是它也不适用于IList<T>,原因是IList<T>包含添加和更改列表中项目的方法 - 正如Jon Skeet的新答案所说。

能够将“joe”列表转换为“human”的唯一方法是,如果界面纯粹是设计的只读,如下所示:

public interface IListReader<out T> : IEnumerable<T>
{
    T this[int index] { get; }
    int Count { get; }
}

即使是Contains(T item)方法也是不允许的,因为当您将IListReader<joe>投射到IListReader<human>时,Contains(human item)中没有IListReader<joe>方法。

您可以使用GoInterface“强制”从IList<joe>转换为IListReader<joe>IListReader<human>甚至IList<human>。但是如果列表足够小可以复制,那么更简单的解决方案就是将其复制到新的List<human>中,正如Paw指出的那样。

答案 4 :(得分:0)

如果我允许您将List<Joe> joes概括为......

  List<Human> humans = joes;

...现在,两个引用humansjoes指向完全相同的列表。上述分配后的代码无法阻止将另一种类型的人的实例(例如水管工)插入/添加到列表中。鉴于class Plumber: Human {}

humans.Add(new Plumber()); // Add() now accepts any Human not just a Joe 

humans引用的列表现在包含了joes和管道工。请注意,引用joes仍引用相同的列表对象。 现在如果我使用引用joes从列表对象中读取,我可能会弹出一个管道工而不是一个乔。不知道水管工和乔是否可以隐含地相互转换......所以我从管道中找到一个水管工而不是一个乔会打破类型安全。通过参考一系列的工作,肯定不欢迎水管工。

然而,在最近的C#版本中,通过实现类型参数上有out修饰符的通用接口,可以解决泛型类/集合的这种限制。假设我们现在有ABag<T> : ICovariable<out T>。 out修饰符仅限制T到输出位置(例如,方法返回类型)。你不能在袋子里输入任何T.你只能读出它们。 这允许我们将joes推广到ICovariable<Human>,而不必担心将Plumber插入其中,因为界面不允许这样做。我们现在可以写...

ICovariable<Human> humans = joes ; // now its good !
humans.Add(new Plumber()); // error