为什么协变和逆变不支持价值types

IEnumerable<T>co-variant,但不支持值types,只是引用types。 下面的简单代码编译成功:

 IEnumerable<string> strList = new List<string>(); IEnumerable<object> objList = strList; 

但是从stringint会得到编译错误:

 IEnumerable<int> intList = new List<int>(); IEnumerable<object> objList = intList; 

原因在MSDN中解释:

差异仅适用于参考types; 如果为变体types参数指定值types,则该types参数对于生成的构造types是不变的。

我search了一下,发现有些问题提到了原因是值types和引用types之间的装箱 。 但是,为什么拳击就是这个原因,这并不能说明我的心意?

有人能给一个简单和详细的解释,为什么协变和逆变不支持价值types, 拳击如何影响这个?

基本上,当CLR可以确保它不需要对值进行任何表示性变更时,方差就适用。 引用都看起来是一样的 – 所以你可以使用一个IEnumerable<string>作为一个IEnumerable<object>而不需要改变表示forms。 本地代码本身并不需要知道你在做什么值,只要基础设施已经保证它肯定是有效的。

对于值types,这是行不通的 – 将IEnumerable<int>当作一个IEnumerable<object> ,使用该序列的代码将不得不知道是否执行装箱转换。

一般来说,您可能想要阅读Eric Lippert 关于代表性和身份的博客文章,以了解更多关于此主题的内容。

编辑:自己重读埃里克的博客文章,它至less与身份代表,虽然两者是链接的。 尤其是:

这就是接口和委托types的协变和逆变换要求所有不同types的参数都是引用types的原因。 为确保变体引用转换始终是保持身份的,涉及types参数的所有转换也必须是保持身份的。 确保types参数上的所有非平凡转换都是保持标识的最简单方法是将它们限制为引用转换。

如果您考虑底层表示(即使这是一个实现细节),也许更容易理解。 这是一个string的集合:

 IEnumerable<string> strings = new[] { "A", "B", "C" }; 

您可以将这些strings视为具有以下表示forms:

 [0]:string参考 - >“A”
 [1]:string参考 - >“B”
 [2]:string参考 - >“C”

它是三个元素的集合,每个元素都是对string的引用。 您可以将其转换为一组对象:

 IEnumerable<object> objects = (IEnumerable<object>) strings; 

基本上它是相同的表示,除了现在引用是对象引用:

 [0]:对象引用 - >“A”
 [1]:对象引用 - >“B”
 [2]:对象引用 - >“C”

表示是一样的。 这些引用只是被区别对待。 你不能再访问string.Length属性,但你仍然可以调用object.GetHashCode() 。 将它与一组ints进行比较:

 IEnumerable<int> ints = new[] { 1, 2, 3 }; 
 [0]:int = 1
 [1]:int = 2
 [2]:int = 3

要将其转换为IEnumerable<object> ,必须通过装入整数来转换数据:

 [0]:对象引用 - > 1
 [1]:对象引用 - > 2
 [2]:对象引用 - > 3

这种转换需要的不仅仅是演员。

我认为一切都从LSP (Liskov Substitution Principle)开始,

如果q(x)是关于typesT的对象x可certificate的属性,则对于typesS的对象y,q(y)应为真,其中S是T的子types。

但是值types,例如int不能替代C#object 。 certificate非常简单:

 int myInt = new int(); object obj1 = myInt ; object obj2 = myInt ; return ReferenceEquals(obj1, obj2); 

即使将相同的 “引用”分配给对象,这也会返回false

它涉及到一个实现细节:值types实现与引用types不同。

如果强制将值types视为参考types(例如,通过通过界面引用它们,则可以使其变化)。

查看差异的最简单方法是简单地考虑一个Array :一个Valuetypes的数组连续(直接)放在内存中,其中Referencetypes的数组只有内存中的引用(一个指针)是连续的。 被指向的对象被分开分配。

另一个(相关的)问题(*)是(几乎)所有引用types都有相同的表示方式,并且许多代码不需要知道types之间的差异,因此可以共享和反方差实施 – 往往只是省略额外的types检查)。

(*)可能会被视为同一个问题…