协变在C#

是否有可能在C#4.0中将List<Subclass> List<Superclass>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]是不合法的,因为这个项目已经被拒绝了,每个人都会很高兴。 现在唯一的select是创build一个新的列表

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

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

显然,最后一行(如果不是更早的) 必须失败 – 因为Fred不是JoeList<T>的不变性在编译时防止了这个错误,而不是执行时间。

实例化一个将joes作为input的新人类列表:

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

不可以。C#4.0的协变特性只支持接口和委托。 不支持像List<T>这样的具体types。

不,正如Jared所说,C#4.0的协变特性只支持接口和委托。 然而它也不适用于IList<T> ,原因是IList<T>包含添加和更改列表中的项目的方法 – 正如Jon Skeet的新答案所述。

能够将“joe”列表转换为“human”的唯一方法是如果接口纯粹是通过devise只读的,如下所示:

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

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

你可以用一个GoInterface从IList<joe> IListReader<joe>IListReader<joe>IListReader<human>甚至IList<human> 。 但是,如果列表足够小以便复制,则更简单的解决scheme就是将其复制到新的List<human> ,正如Paw指出的那样。

如果我允许你的List<Joe> joes被推广为…

  List<Human> humans = joes; 

…现在,两个参考joesjoes指向完全相同的列表。 上述分配之后的代码没有办法阻止将另一种types的人(例如pipe道工)的实例插入/添加到列表中。 鉴于class Plumber: Human {}

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

现在humans引用的列表包含乔和水pipe工。 请注意,相同的列表对象仍由引用joes引用。 现在,如果我使用参考joes从列表对象读取,我可能会popup一个水pipe工,而不是一个乔 。 水pipe工和乔不知道是隐式的相互转换…所以我从一个水pipe工,而不是从名单上的乔得到打破types安全。 一个水pipe工当然不欢迎通过参考一个乔的名单。

然而,在最近的C#版本中,通过实现一个generics接口,其types参数有一个out修饰符,可以解决generics类/集合的这种限制。 假设我们现在有ABag<T> : ICovariable<out T> 。 out修饰符仅将T限制在输出位置(例如方法返回types)。 你不能input任何T到袋子里。 你只能读出来。 这允许我们将joes概括为一个ICovariable<Human>而不用担心插入水pipe工,因为接口不允许这样做。 我们现在可以写…

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