【问题标题】:List, array and IEnumerable covariance列表、数组和 IEnumerable 协方差
【发布时间】:2013-06-12 09:23:18
【问题描述】:

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

数组协方差

假设 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<T> 不是。只有不可变的集合类应该是协变的。
  • @TimSchmelter:虽然正确,但该论点也适用于假设 1.3,因此并不能解释为什么 1.3 有效。 ReSharper 在 1.3 的场景中正确警告

标签: c# arrays list ienumerable covariance


【解决方案1】:

列表协方差不安全:

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&lt;T&gt; 接口修改集合,因此将List&lt;string&gt; 键入为IEnumerable&lt;object&gt; 是安全的。

【讨论】:

  • 是的,最令人讨厌的是他们允许数组协方差。他们不应该有。
  • @MatthewWatson:如果系统包含“可读数组引用”(协变)、“可写数组引用”(逆变)、“项目可交换”的类型,数组协变和逆变可能是一件好事数组引用”(与类型无关)和“可排序数组引用”(可读和项目可交换的协变组合)。通用的Sort 例程可以接受后一种类型。然而,缺少这些特定类型并不能消除通用排序例程需要协变可排序类型的事实。因此,System.Array 是协变和可排序的。
  • @supercat 好吧,语言设计者确实说“这种特殊的协方差被破坏了”——你看过 Lee 发布的链接了吗?
  • @MatthewWatson:编写高效的通用数组排序例程要求框架支持数组协方差或泛型类型。 .NET 的设计者有以下选择:(1) 拥有一个框架,其中不可能在用户代码中编写高效的通用排序例程,(2) 在通用支持完成之前不发布框架,(3) 包括各种数组引用类型,或者 (4) 允许数组协方差作为一个组合,使用户代码可以编写高效的通用排序例程。
  • @supercat 或者,在这种情况下,它可能来自于阅读一位从事语言设计工作的人的博客。 :)
【解决方案2】:

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

String[] 可以传递给期望Object[] 的代码的原因是前者包含“对从类型String 派生的类型的堆对象实例的引用”,而后者同样适用于类型@987654331 @。由于String 派生自Object,因此对派生自String 的类型的堆对象的引用也将是对派生自Object 的堆对象的引用,并且String[] 将包含对派生自Object 的堆对象——正是期望从Object[] 读取的代码。相比之下,因为int[] [即System.Int32[]] 不包含对 Int32 类型的堆对象实例的引用,其内容将不符合预期 Object[] 的代码的预期。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-06
    • 1970-01-01
    • 2015-06-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多