在C#中,任何用户定义的struct
自动成为 System.Struct System.ValueType
和 System.Struct {{1}的子类。 }是System.ValueType
的子类。
但是当我们为对象类型引用分配一些结构时,它会被装箱。例如:
System.Object
所以我的问题是:如果struct A
{
public int i;
}
A a;
object obj = a; // boxing takes place here
是A
的后代,编译器不能将它上传到对象类型而不是装箱吗?
答案 0 :(得分:37)
结构是一种值类型。 System.Object
是引用类型。运行时以不同方式存储和处理值类型和引用类型。要将值类型视为引用类型,必须将其设置为加框。从低级别的角度来看,这包括将值从最初所在的堆栈复制到堆上新分配的内存,该内存还包含一个对象头。引用类型需要额外的头来解析它们的vtable以启用虚拟方法调度和其他引用类型相关的功能(请记住,堆栈上的结构只是一个值而且它没有类型信息;它不包含任何类似vtable和can的内容不能直接用于解析动态调度的方法。此外,要将某些东西视为引用类型,您必须有一个引用(指针),而不是它的原始值。
所以我的问题是 - 如果A是System.Object的后代,是不是可以将它编译为对象类型而不是拳击?
在较低级别,值不会继承任何内容。实际上,正如我之前所说,它并不是一个真正的对象。 A派生自System.ValueType
而后者派生自System.Object
的事实是在您的编程语言(C#)的抽象级别定义的东西,C#确实很好地隐藏了拳击操作。您没有明确提及任何内容来设置值,因此您可以简单地认为编译器已为您“上传”了该结构。它使值的继承和多态的幻觉,而多态行为所需的工具都不是由它们直接提供的。
答案 1 :(得分:17)
这是我更喜欢考虑它的方式。考虑包含32位整数的变量的实现。当被视为值类型时,整个值适合32位存储。这就是值类型:存储只包含构成值的位,仅此而已。
现在考虑包含对象引用的变量的实现。该变量包含一个“引用”,可以通过多种方式实现。它可以是垃圾收集器结构的句柄,也可以是托管堆上的地址,或者其他什么。但它可以让你找到一个对象。这就是引用类型:与引用类型变量关联的存储包含一些允许引用对象的位。
显然,这两件事完全不同。现在假设你有一个object类型的变量,并且你希望将int类型变量的内容复制到其中。你怎么做呢?构成整数的32位不是这些“引用”之一,它只是一个包含32位的桶。引用可以是进入托管堆的64位指针,也可以是垃圾收集器数据结构中的32位句柄,或者您可以想到的任何其他实现,但32位整数只能是32位整数。
那么你在那个场景中做的就是装整数:你创建一个包含整数存储的新对象,然后存储对新对象的引用。
只有在您希望(1)具有统一类型系统,以及(2)确保32位整数消耗32位内存时才需要拳击。如果你愿意拒绝其中任何一个,那么你就不需要拳击;我们不愿意拒绝那些,所以拳击是我们被迫忍受的。
答案 2 :(得分:4)
虽然.NET的设计者当然不需要包含C# Language Specification的拳击部分4.3,但很好地解释了它背后的意图,IMO:
拳击和拆箱可实现统一 其中a。类型系统的视图 任何类型的价值最终都可以 作为对象对待。
因为值类型不是引用类型(最终是System.Object),所以存在装箱行为以便拥有统一类型系统,其中任何的值可以表示为对象
这与C ++不同,其中类型系统不统一,所有类型都没有通用的基本类型。
答案 3 :(得分:0)
struct
是一种设计的值类型,因此在转换为引用类型时需要加框。 struct
来自System.ValueType
,其术语来自System.Object
。
struct 是对象的后代这一事实并不意味着......因为CLR在运行时以不同于引用类型的方式处理structs
。
答案 4 :(得分:0)
问题解答后,我会提出与该主题相关的一些“技巧”:
struct
可以实现接口。如果将值类型传递给期望此值类型实现的接口的函数,则该值通常会被装箱。使用泛型可以避免装箱:
interface IFoo {...}
struct Bar : IFoo {...}
void boxing(IFoo x) { ... }
void byValue<T>(T x) : where T : IFoo { ... }
var bar = new Bar();
boxing(bar);
byValue(bar);
答案 5 :(得分:0)
“如果
struct A
是System.Object
的后代,编译器是否不能将其上传而不是装箱?”
不,仅仅因为根据C#语言的定义,在这种情况下“up-casting”是拳击。
C#的语言规范包含(在第13章中)所有可能类型转换的目录。所有这些转化都按特定方式进行分类(例如数字转换,参考转换等)。
从类型S
到其超类型T
存在隐式类型转换,但这些转换仅针对类类型{{1}中的模式“定义}到引用类型S
“。由于您的T
不是类类型,因此无法在您的示例中应用这些转换。
也就是说,struct A
(间接地)从A
派生(虽然正确)这一事实在这里无关紧要。相关的是object
是结构值类型。
仅现有转化,其匹配模式“从值类型A
到其参考超类型A
”被归类为拳击转换。因此,从object
到struct
的每次转化都是按照定义被视为装箱。