为什么结构需要装箱?

在C#中,任何用户定义的struct都自动成为System.Struct System.ValueTypeSystem.Struct的子类System.ValueTypeSystem.Object的子类。

但是当我们为对象类型引用分配一些结构时,它会被装箱。 例如:

 struct A { public int i; } A a; object obj = a; // boxing takes place here 

所以我的问题是:如果ASystem.Object的后代,编译器是否不能将其上传到对象类型而不是装箱?

结构是一种值类型。 System.Object是引用类型。 运行时以不同方式存储和处理值类型和引用类型。 要将值类型视为引用类型,必须对其进行装箱。 从低级别的角度来看,这包括将值从最初所在的堆栈复制到堆上新分配的内存,该内存还包含一个对象头。 引用类型需要额外的头来解析它们的vtable以启用虚拟方法调度和其他引用类型相关的function(请记住,堆栈上的结构只是一个值,它没有类型信息;它不包含任何类似vtable和can的内容不能直接用于解析动态调度的方法。 此外,要将某些东西视为引用类型,您必须有一个引用 (指针),而不是它的原始值。

所以我的问题是 – 如果A是System.Object的后代,是不是可以将它编译为对象类型而不是拳击?

在较低级别,值不会inheritance任何内容。 实际上,正如我之前所说,它并不是一个真正的对象。 A派生自System.ValueType ,而System.ValueType又是从System.Object派生的,这是在编程语言(C#)的抽象级别定义的东西,而C#确实很好地隐藏了拳击操作。 您没有明确提及任何内容来设置值,因此您可以简单地认为编译器已为您“上传”了该结构。 它使得值的inheritance和多态的错觉 ,而多态行为所需的工具都不是由它们直接提供的。

这是我更喜欢考虑的方式。 考虑包含32位整数的变量的实现。 当被视为值类型时,整个值适合32位存储。 这就是值类型:存储只包含构成值的位,仅此而已。

现在考虑包含对象引用的变量的实现。 该变量包含“引用”,可以通过多种方式实现。 它可以是垃圾收集器结构的句柄,也可以是托管堆上的地址,或者其他什么。 但它可以让你找到一个对象。 这就是引用类型:与引用类型变量关联的存储包含一些允许您引用对象的位。

显然,这两件事完全不同。

现在假设你有一个object类型的变量,并且你希望将int类型变量的内容复制到其中。 你怎么做呢? 构成整数的32位不是这些“参考”事物之一,它只是一个包含32位的桶。 引用可以是进入托管堆的64位指针,也可以是垃圾收集器数据结构中的32位句柄,或者您可以想到的任何其他实现,但32位整数只能是32位整数。

因此,您在该场景中执行的操作是对整数进行装箱:您创建一个包含整数存储的新对象,然后存储对新对象的引用。

只有当你想要(1)拥有统一的类型系统,并且(2)确保32位整数消耗32位内存时,才需要拳击。 如果你愿意拒绝其中任何一个,那么你就不需要拳击; 我们不愿意拒绝那些,所以拳击是我们被迫忍受的。

虽然.NET的设计者当然不需要包含拳击部分4.3的C#语言规范很好地解释了它背后的意图,IMO:

装箱和拆箱可以实现类型系统的统一视图,其中任何类型的值最终都可以被视为对象。

因为值类型不是引用类型(最终是System.Object),所以存在装箱行为以便具有统一类型系统,其中任何东西的值可以表示为对象。

这与C ++不同,其中类型系统不统一,所有类型都没有通用的基本类型。

struct是一种设计的值类型,因此在转换为引用类型时需要加框。 struct派生自System.ValueType ,其在术语中派生自System.Object

结构对象后代这一事实并不意味着……因为CLR在运行时处理的structs与引用类型不同。

在问题得到解答之后,我将提出与该主题相关的一些“技巧”:

struct s可以实现接口。 如果将值类型传递给期望此值类型实现的接口的函数,则该值通常会被装箱。 使用generics你可以避免拳击:

 interface IFoo {...} struct Bar : IFoo {...} void boxing(IFoo x) { ... } void byValue(T x) : where T : IFoo { ... } var bar = new Bar(); boxing(bar); byValue(bar); 

“如果struct ASystem.Object的后代,那么编译器不能将它上传而不是装箱吗?”

不,仅仅因为根据C#语言的定义,在这种情况下“up-casting” 拳击。

C#的语言规范包含(在第13章中)所有可能的类型转换的目录。 所有这些转换都以特定方式分类(例如数字转换,参考转换等)。

  1. 存在从类型S到其超类型T隐式类型转换,但这些转换仅针对“从类类型S到引用类型T的模式定义。 由于struct A不是类类型,因此无法在示例中应用这些转换。

    也就是说, A ((间接)从object (虽然正确)派生的事实在这里简直无关紧要。 相关的是A是结构值类型。

  2. 与模式“从值类型A到其引用超类型object匹配的唯一现有转换被归类为装箱转换。 因此,从structobject每次转换根据定义都被认为是装箱。