列表,数组和IEnumerable协方差

时间:2013-06-12 09:23:18

标签: c# arrays list ienumerable covariance

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

数组协方差

假设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>而不是List<string>
  2. 为什么IEnumerable<object>可以通过List<object>而不能通过{{1}}?

2 个答案:

答案 0 :(得分: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#中的数组协方差被识别为as a mistake,并且自版本1以来一直存在。

由于无法通过IEnumerable<T>界面修改集合,因此可以安全地将List<string>键入IEnumerable<object>

答案 1 :(得分:-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[]的代码期望。