列表,数组和IEnumerable协方差

本文关键字:方差 IEnumerable 数组 列表 | 更新日期: 2023-09-27 18:18:04

为了更好地解释我的问题的背景,我将从几个假设开始:

阵列协方差

假设1.1

值类型的数组不是协变的。int[]不能通过object[]

假设1.2

引用类型的数组与有效的IEnumerable是协变的。string[]可以代替IEnumerable<object>)。

假设1.3

引用类型的数组与有效的协变数组是协变的。string[]可以通过object[]

协方差

列表

设2.1(同1.1)

值类型的列表不是协变的。List<int>不能通过List<object> .

设2.2(同1.2)

引用类型的列表与有效的IEnumerable是协变的。List<string>可以通过IEnumerable<object>)。

假设2.3(不同于1.3)

引用类型的列表不与有效的协变List协变。List<string>不能通过List<object>)。


我的问题涉及假设1.3,2.2和2.3。具体来说:

  1. 为什么string[]可以通过object[]List<string>不能通过List<object> ?
  2. 为什么List<string>可以通过IEnumerable<object>而不能通过List<object> ?

列表,数组和IEnumerable协方差

列表协方差不安全:

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>是安全的。

数组是协变的,但是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[]的代码的期望。