列表,数组和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。具体来说:
- 为什么
string[]
可以通过object[]
而List<string>
不能通过List<object>
? - 为什么
List<string>
可以通过IEnumerable<object>
而不能通过List<object>
?
列表协方差不安全:
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[]
的代码的期望。