为什么协方差和逆变不支持值类型

cuo*_*gle 144 .net c# covariance contravariance c#-4.0

IEnumerable<T>是共变体,但它不支持值类型,仅支持引用类型.以下简单代码编译成功:

IEnumerable<string> strList = new List<string>();
IEnumerable<object> objList = strList;
Run Code Online (Sandbox Code Playgroud)

但是从更改string到int将得到编译错误:

IEnumerable<int> intList = new List<int>();
IEnumerable<object> objList = intList;
Run Code Online (Sandbox Code Playgroud)

原因在MSDN中解释:

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

我搜索过并发现有些问题提到的原因是值类型和引用类型之间的装箱.但它仍然不清楚我的想法为什么拳击是什么原因?

有人可以给出一个简单而详细的解释为什么协方差和逆变不支持值类型以及拳击如何影响这个?

Jon*_*eet 122

基本上,当CLR可以确保它不需要对值进行任何代表性更改时,适用方差.引用看起来都一样 - 所以你可以使用a IEnumerable<string>作为一个IEnumerable<object>没有任何表示的变化; 本机代码本身不需要知道你在使用这些值做什么,只要基础结构确保它肯定是有效的.

对于值类型,这不起作用 - 将a处理IEnumerable<int>为IEnumerable<object>,使用序列的代码必须知道是否执行装箱转换.

您可能希望阅读Eric Lippert 关于表示和身份的博客文章,以获取有关此主题的更多信息.

编辑:自己重读了Eric的博客文章,它至少与身份和表现一样多,尽管这两者是相互关联的.特别是:

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

  • @CuongLe:嗯,这是某种意义上的实施细节,但我认为这是限制的根本原因. (5认同)
  • @AndréCaron:Eric的博客文章在这里很重要 - 它不仅仅是代表性,还有身份保护.但是表示保存意味着生成的代码根本不需要关心这一点. (2认同)
  • int如何不是对象的子类型?Int32继承自System.ValueType,后者继承自System.Object。 (2认同)

Mar*_*age 10

如果你考虑底层表示,也许更容易理解(即使这确实是一个实现细节).这是一个字符串集合:

IEnumerable<string> strings = new[] { "A", "B", "C" };
Run Code Online (Sandbox Code Playgroud)

您可以将其strings视为具有以下表示:

[0] : string reference -> "A"
[1] : string reference -> "B"
[2] : string reference -> "C"

它是三个元素的集合,每个元素都是对字符串的引用.您可以将其强制转换为对象集合:

IEnumerable<object> objects = (IEnumerable<object>) strings;
Run Code Online (Sandbox Code Playgroud)

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

[0] : object reference -> "A"
[1] : object reference -> "B"
[2] : object reference -> "C"

表示是一样的.引用只是区别对待; 您无法再访问该string.Length酒店,但仍可以致电酒店object.GetHashCode().将此与一组int进行比较:

IEnumerable<int> ints = new[] { 1, 2, 3 };
Run Code Online (Sandbox Code Playgroud)
[0] : int = 1
[1] : int = 2
[2] : int = 3

要将此转换IEnumerable<object>为数据必须通过装箱整数进行转换:

[0] : object reference -> 1
[1] : object reference -> 2
[2] : object reference -> 3

此转换需要的不仅仅是演员表.

  • 拳击不仅仅是一个"实施细节".盒装值类型以与类对象相同的方式存储,并且就外部世界而言,就像类对象一样.唯一的区别是,在盒装值类型的定义中,`this`指的是一个结构,其字段覆盖存储它的堆对象的那些,而不是引用保存它们的对象.盒装值类型实例没有干净的方法来获取对封闭堆对象的引用. (2认同)

Tig*_*ran 7

我认为一切都始于LSP(Liskov Substitution Principle)的定义,其中包括:

如果q(x)是可证明关于类型T的对象x的属性,那么对于类型S的对象y,q(y)应该为真,其中S是T的子类型.

但是,值类型,例如int不能代用object在C#.证明非常简单:

int myInt = new int();
object obj1 = myInt ;
object obj2 = myInt ;
return ReferenceEquals(obj1, obj2);
Run Code Online (Sandbox Code Playgroud)

false即使我们为对象分配相同的 "引用",这也会返回.

  • @André:C#不是Java.C#的`int`关键字是BCL的`System.Int32`的别名,它实际上是`object`的子类型(`System.Object`的别名).实际上,`int`的基类是`System.ValueType`,它的基类是`System.Object`.尝试评估以下表达式并查看:`typeof(int).BaseType.BaseType`.这里`ReferenceEquals`返回false的原因是`int`被装入两个单独的框中,并且每个框的标识对于任何其他框都是不同的.因此,无论盒装值如何,两个装箱操作总是产生两个永远不会相同的对象. (20认同)