简体   繁体   English

为什么结构需要装箱?

[英]Why do structs need to be boxed?

In C#, any user-defined struct is automatically a subclass of System.Struct System.ValueType and System.Struct System.ValueType is a subclass of System.Object . 在C#中,任何用户定义的struct都自动成为 System.Struct System.ValueType System.Struct 的子类System.ValueTypeSystem.Object的子类。

But when we assign some struct to object-type reference it gets boxed. 但是当我们为对象类型引用分配一些结构时,它会被装箱。 For example: 例如:

struct A
{
    public int i;
}

A a;
object obj = a;  // boxing takes place here

So my question is: if A is an descendant of System.Object , can't the compiler up-cast it to object type instead of boxing? 所以我的问题是:如果ASystem.Object的后代,编译器不能将它上传到对象类型而不是装箱吗?

A struct is a value type. 结构是一种值类型。 System.Object is a reference type. System.Object是引用类型。 Value types and reference types are stored and treated differently by the runtime. 运行时以不同方式存储和处理值类型和引用类型。 For a value type to be treated as a reference type, it's necessary for it to be boxed. 要将值类型视为引用类型,必须将其设置为加框。 From a low level perspective, this includes copying the value from the stack where it originally lives to the newly allocated memory on the heap, which also contains an object header. 从低级别的角度来看,这包括将值从最初所在的堆栈复制到堆上新分配的内存,该内存还包含一个对象头。 Additional headers are necessary for reference types to resolve their vtables to enable virtual method dispatches and other reference type related features (remember that a struct on stack is just a value and it has zero type information; it doesn't contain anything like vtables and can't be directly used to resolve dynamically dispatched methods). 引用类型需要额外的头来解析它们的vtable以启用虚拟方法调度和其他引用类型相关的功能(请记住,堆栈上的结构只是一个值而且它没有类型信息;它不包含任何类似vtable和can的内容不能直接用于解析动态调度的方法。 Besides, to treat something as a reference type, you have to have a reference (pointer) to it, not the raw value of it. 此外,要将某些东西视为引用类型,您必须有一个引用 (指针),而不是它的原始值。

So my question is - if A is an descendant of System.Object, can't compiler upcast it to object type instead of boxing? 所以我的问题是 - 如果A是System.Object的后代,是不是可以将它编译为对象类型而不是拳击?

At a lower level, a value does not inherit anything. 在较低级别,值不会继承任何内容。 Actually, as I said before, it's not really an object. 实际上,正如我之前所说,它并不是一个真正的对象。 The fact that A derives from System.ValueType which in turn derives from System.Object is something defined at the abstraction level of your programming language (C#) and C# is indeed hiding the boxing operation from you pretty well. A派生自System.ValueType ,而System.ValueType又派生自System.Object是在编程语言(C#)的抽象级别定义的,而C#确实很好地隐藏了装箱操作。 You don't mention anything explicitly to box the value so you can simply think the compiler has "upcasted" the structure for you. 您没有明确提及任何内容来设置值,因此您可以简单地认为编译器已为您“上传”了该结构。 It's making the illusion of inheritance and polymorphism for values while none of the tools required for polymorphic behavior is directly provided by them. 它使得值的继承和多态的错觉 ,而多态行为所需的工具都不是由它们直接提供的。

Here's how I prefer to think about it. 这是我更喜欢考虑它的方式。 Consider the implementation of a variable containing a 32 bit integer. 考虑包含32位整数的变量的实现。 When treated as a value type, the entire value fits into 32 bits of storage. 当被视为值类型时,整个值适合32位存储。 That's what a value type is: the storage contains just the bits that make up the value, nothing more, nothing less. 这就是值类型:存储只包含构成值的位,仅此而已。

Now consider the implementation of a variable containing an object reference. 现在考虑包含对象引用的变量的实现。 The variable contains a "reference", which could be implemented in any number of ways. 该变量包含一个“引用”,可以通过多种方式实现。 It could be a handle into a garbage collector structure, or it could be an address on the managed heap, or whatever. 它可以是垃圾收集器结构的句柄,也可以是托管堆上的地址,或者其他什么。 But it's something which allows you to find an object. 但它可以让你找到一个对象。 That's what a reference type is: the storage associated with a variable of reference type contains some bits that allow you to reference an object. 这就是引用类型:与引用类型变量关联的存储包含一些允许引用对象的位。

Clearly those two things are completely different. 显然,这两件事完全不同。

Now suppose you have a variable of type object, and you wish to copy the contents of a variable of type int into it. 现在假设你有一个object类型的变量,并且你希望将int类型变量的内容复制到其中。 How do you do it? 你怎么做呢? The 32 bits that make up an integer aren't one of these "reference" things, it's just a bucket that contains 32 bits. 构成整数的32位不是这些“引用”之一,它只是一个包含32位的桶。 References could be 64 bit pointers into the managed heap, or 32 bit handles into a garbage collector data structure, or any other implementation you can think of, but a 32 bit integer can only be a 32 bit integer. 引用可以是进入托管堆的64位指针,也可以是垃圾收集器数据结构中的32位句柄,或者您可以想到的任何其他实现,但32位整数只能是32位整数。

So what you do in that scenario is you box the integer: you make a new object that contains storage for an integer, and then you store a reference to the new object. 那么你在这种情况下所做的就是选中整数:你创建一个包含整数存储的新对象,然后存储对新对象的引用。

Boxing is only necessary if you want to (1) have a unified type system, and (2) ensure that a 32 bit integer consumes 32 bits of memory. 只有当你想要(1)拥有统一的类型系统,并且(2)确保32位整数消耗32位内存时,才需要拳击。 If you're willing to reject either of those then you don't need boxing; 如果你愿意拒绝其中任何一个,那么你就不需要拳击; we are not willing to reject those, and so boxing is what we're forced to live with. 我们不愿意拒绝那些,所以拳击是我们被迫忍受的。

While the designers of .NET certainly didn't need to include boxing section 4.3 of the C# Language Specification explains the intent behind it quite well, IMO: 虽然.NET的设计者当然不需要包含拳击部分4.3的C#语言规范很好解释了它背后的意图,IMO:

Boxing and unboxing enables a unified view of the type system wherein a value of any type can ultimately be treated as an object. 装箱和拆箱可以实现类型系统的统一视图,其中任何类型的值最终都可以被视为对象。

Because value types are not reference types (which System.Object ultimately is), the act of boxing exists in order to have a unified type system where the value of anything can be represented as an object. 因为值类型不是引用类型(最终是System.Object),所以存在装箱行为以便具有统一类型系统,其中任何东西的值可以表示为对象。

This is different from say, C++ where the type system isn't unified, there isn't a common base type for all types. 这与C ++不同,其中类型系统不统一,所有类型都没有通用的基本类型。

"If struct A is an descendant of System.Object , can't the compiler up-cast it instead of boxing?" “如果struct ASystem.Object的后代,那么编译器不能将它上传而不是装箱吗?”

No, simply because according to the definition of the C# language, "up-casting" in this case is boxing. 不,仅仅因为根据C#语言的定义,在这种情况下“up-casting” 装箱。

The language specification for C# contains (in chapter 13) a catalogue of all possible type conversions. C#的语言规范包含(在第13章中)所有可能的类型转换的目录。 All these conversions are categorized in a specific fashion (eg numeric conversions, reference conversions, etc.). 所有这些转换都以特定方式进行分类(例如数字转换,参考转换等)。

  1. There are implicit type conversions from a type S to its super-type T , but these are only defined for the pattern "from a class type S to a reference type T " . 存在从类型S到其超类型T隐式类型转换,但这些转换仅针对“从类类型S到引用类型T的模式定义。 Because your struct A is not a class type, these conversions cannot be applied in your example. 由于struct A不是类类型,因此无法在示例中应用这些转换。

    That is, the fact that A is (indirectly) derived from object (while correct) is simply irrelevant here. 也就是说, A ((间接)从object派生(虽然正确)这一事实在这里简直无关紧要。 What is relevant is that A is a struct value type. 相关的是A是结构值类型。

  2. The only existing conversion that matches the pattern "from a value type A to its reference super-type object " is categorized as a boxing conversion. 与模式“从值类型A到其引用超类型object匹配的唯一现有转换被归类为装箱转换。 Thus every conversion from a struct to object is by definition considered boxing. 因此,从structobject每次转换根据定义都被认为是装箱。

struct is a value-type by design, hence it needs to be boxed when turned into a reference type. struct是一种设计的值类型,因此在转换为引用类型时需要加框。 struct derives from System.ValueType , which in term derives from System.Object . struct派生自System.ValueType ,它在术语派生自System.Object

The mere fact that struct is a descendant of object, does not mean much..since the CLR deals with structs differently at runtime than a reference type. 结构对象后代这一事实并不意味着......因为CLR在运行时处理的structs与引用类型不同。

After the question has been answered I'll present a little "trick" related to that topic: 在问题得到解答之后,我将提出与该主题相关的一些“技巧”:

struct s can implement interfaces. struct s可以实现接口。 If you pass a value type to a function that expects an interface that this value type implements the value normally gets boxed. 如果将值类型传递给期望此值类型实现的接口的函数,则该值通常会被装箱。 Using generics you can avoid the boxing: 使用泛型你可以避免拳击:

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);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM