列表,数组和IEnumerable协变

6
我将从几个前提条件开始,以更好地解释我的问题背景:
数组协变性
前提1.1:值类型的数组不是协变的。int[]不能作为object[]。
前提1.2:引用类型的数组与有效的IEnumerable协变。string[]可以通过IEnumberable。
前提1.3:引用类型的数组与有效的协变数组协变。string[]可以通过object[]。
列表协变性
前提2.1(与1.1相同):值类型的列表不是协变的。List不能通过List。
前提2.2(与1.2相同):引用类型的列表与有效的IEnumerable协变。List可以通过IEnumerable。
前提2.3(与1.3不同):引用类型的列表与有效的协变List不协变。List不能通过List。
我的问题涉及到前提1.3、2.2和2.3。具体来说:
1. 为什么string[]可以作为object[],但List不能作为List?
2. 为什么List可以通过IEnumerable,但不能通过List?

2
因为列表是可变的,而数组或 IEnumerable<T> 不是。只有不可变的集合类应该是协变的。 - Tim Schmelter
2
@TimSchmelter:虽然如此,这个论点也适用于公设1.3,因此并不能解释为什么1.3是有效的。在1.3的情况下,ReSharper会正确地发出警告。 - Daniel Hilgarth
2个回答

13

列表协变不安全:

List<string> strings = new List<string> { "a", "b", "c" };
List<object> objects = strings;
objects.Add(1);              //

数组协变性由于同样的原因也是不安全的:

string[] strings = new[] { "a", "b", "c" };
object[] objects = strings;
objects[0] = 1;              //throws ArrayTypeMismatchException

C#中的数组协变被认为是一个错误,自版本1以来一直存在。

由于集合不能通过IEnumerable<T>接口进行修改,因此将List<string>作为IEnumerable<object>类型进行定义是安全的。


2
是的,让数组协变这件事最让人恼火了。他们不应该这么做。 - Matthew Watson
@MatthewWatson:如果系统包括“可读数组引用”(协变型)、“可写数组引用”(逆变型)、“可交换项数组引用”(类型无关)和“可排序数组引用”(可变型的可读和可交换项组合),那么数组协变性和逆变性本可以是一件好事。通用的Sort例程可以接受后者类型。然而,缺乏这样具体的类型并不排除通用排序例程需要一个协变可排序类型的事实。因此,System.Array被设计成协变和可排序的。 - supercat
1
@supercat,编程语言的设计者确实说“这种特定的协变是有问题的”- 你看了Lee发布的链接吗? - Matthew Watson
@MatthewWatson:我读过无数篇文章,其中某些设计者承认犯了错误。往往情况是,这个“错误”在当时可能是解决问题的最佳方案。在某些情况下(比如Hoare的空指针),普遍认为某个东西是“错误”的信念源于未能认识到其他提出的替代方案的局限性。 - supercat
1
@supercat 或者,在这种情况下,可能是通过阅读语言设计人员之一的博客。 :) - Matthew Watson
显示剩余3条评论

-1

数组是协变的,但是一个 System.Int32[] 不会持有从 System.Object 派生出来的东西的引用。在 .NET 运行时中,每个值类型定义实际上定义了两种东西:一个堆对象类型和一个值(存储位置)类型。堆对象类型派生自System.Object;存储位置类型可以隐式地转换为堆对象类型(后者又派生自 System.Object),但本身并没有派生自 System.Object 或任何其他类。虽然所有数组,包括 System.Int32[] 都是堆对象类型,但是 System.Int32[] 中的各个单独的 元素 是存储位置类型的实例。

一个 String[] 可以传递给期望一个 Object[] 的代码的原因是,前者包含了“引用堆上类型派生自 String 的对象实例”,后者同样适用于类型 Object。由于 String 派生自 Object,对于一个堆上类型派生自 String 的对象实例的引用也将是指向一个派生自 Object 的堆对象的引用,而且一个 String[] 将包含指向派生自 Object 的堆对象的引用——这正是代码从一个 Object[] 中希望读到的内容。相反地,由于一个 int[] [即 System.Int32[]] 不包含引用堆上类型为 Int32 的对象实例,其内容将无法符合期望一个 Object[] 的代码的预期。

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