C#协变性问题

15
在下面的代码中:
interface I1 { }
class CI1: I1 { }

List<CI1> listOfCI1 = new List<CI1>();

IEnumerable<I1> enumerableOfI1 = listOfCI1; //this works

IList<I1> listofI1 = listOfCI1; //this does not

由于协变性,我可以将"listOfCI1"赋值给一个

但是为什么我不能将它赋值给一个呢? 就此而言,我甚至不能做以下操作:

List<I1> listOfI12 = listOfCI1;

协变不应该允许我将派生类型分配给基类型吗?

4个回答

24
简单来说,IList<T> 不是协变的,而 IEnumerable<T> 是。原因如下:
假设 IList<T> 是协变的。下面的代码显然不是类型安全的……但你希望错误出现在哪里?
IList<Apple> apples = new List<Apple>();
IList<Fruit> fruitBasket = apples;
fruitBasket.Add(new Banana()); // Aargh! Added a Banana to a bunch of Apples!
Apple apple = apples[0]; // This should be okay, but wouldn't be

如果想了解方差的详细信息,请参阅Eric Lippert在其博客系列中的相关文章,或观看我在NDC关于方差的视频

基本上,只有在可以保证安全的情况下(以及以保留表示方式的方式)才允许使用方差。这就是为什么你不能将IEnumerable<int>转换为IEnumerable<object> - 装箱转换无法保留表示方式。


@Arnis:谢谢 - 我自己也很喜欢,即使我没能展示我录制的《Hokey Cokey》视频... - Jon Skeet
2
Jon,BCL团队是否添加了out(IEnumerable<out T>:IEnumerable),因为IEnumerable不允许您添加任何新成员(即它是不可变的),这使得它更安全。但另一方面,他们没有在IList<T>上添加“out”,因为它是可变的(这使得可以向底层列表添加不同的实现)。 - Raj Rao
5
@Rajah: 那基本上是正确的。请注意,在IEnumerable<T>中,每个“T”都出现在“输出”位置,从未出现在“输入”位置。因此,对于协变使用“out”,对于逆变使用“in”。这就是编译器和CLR知道它是安全的变体的方式。(实际规则要复杂得多,但这是一个合理的简化。) - Eric Lippert
1
@Neme:完成了。(假设您指的是视频链接。) - Jon Skeet
参见:C# 数组的巨大错误。 - Cory Nelson

5

比较声明(MSDN)

public interface IEnumerable<out T> : IEnumerable

public interface IList<T> : ICollection<T>, IEnumerable<T>, IEnumerable

你看到那个神奇的单词out了吗?这意味着协方差已经启用。


3
更具体地说,这意味着IEnumerable<out T>对于T是协变的。一个接口可能在某些类型参数上是变化的,但在其他类型参数上不是。 - Jon Skeet

3

不行。

否则,您可以将I1的另一种实现添加到一个只应包含C1的列表中。


1

IList<T> 接口不是协变的。


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