为什么结构体需要装箱?

发布于 2024-08-16 11:50:10 字数 449 浏览 14 评论 0 原文

在 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 的后代,编译器不能将其向上转换为对象类型而不是装箱吗?

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.

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

安静 2024-08-23 11:50:10

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

所以我的问题是 - 如果 A 是 System.Object 的后代,编译器不能将其向上转换为对象类型而不是装箱吗?

在较低级别,值不会继承任何内容。事实上,正如我之前所说,它并不是一个真正的对象。 A 派生自 System.ValueType ,而后者又派生自 System.Object ,这一事实是在编程语言 (C#) 的抽象级别定义的,而 C# 确实隐藏了这一点你的拳击操作相当不错。您没有明确提及任何内容来装箱该值,因此您可以简单地认为编译器已为您“向上转换”了该结构。它为值制造了继承和多态性的假象,但多态行为所需的任何工具都不是由它们直接提供的。

A struct is a value type. System.Object is a reference type. 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). 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?

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. 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.

北方。的韩爷 2024-08-23 11:50:10

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

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

显然这两件事是完全不同的。

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

因此,在这种情况下,您要做的就是对整数进行装箱:创建一个包含整数存储的新对象,然后存储对新对象的引用。

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

Here's how I prefer to think about it. Consider the implementation of a variable containing a 32 bit integer. When treated as a value type, the entire value fits into 32 bits of storage. 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. 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. 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.

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. 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.

怀念你的温柔 2024-08-23 11:50:10

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

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

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

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

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:

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.

This is different from say, C++ where the type system isn't unified, there isn't a common base type for all types.

浊酒尽余欢 2024-08-23 11:50:10

struct 在设计上是一种值类型,因此在转换为引用类型时需要对其进行装箱。 struct 派生自 System.ValueType,而后者又派生自 System.Object

struct是对象的后代这一事实并没有多大意义……因为 CLR 在运行时处理struct的方式与引用类型不同。

struct is a value-type by design, hence it needs to be boxed when turned into a reference type. struct derives from System.ValueType, which in term derives from 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.

对你的占有欲 2024-08-23 11:50:10

回答完问题后,我将提出一个与该主题相关的小“技巧”​​:

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

After the question has been answered I'll present a little "trick" related to that topic:

structs can implement interfaces. 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);
懵少女 2024-08-23 11:50:10

“如果struct ASystem.Object的后代,编译器不能向上转换它而不是装箱吗?”

不,只是因为根据 C# 语言的定义,这种情况下的“向上转换”就是装箱。

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

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

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

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


"If struct A is an descendant of System.Object, can't the compiler up-cast it instead of boxing?"

No, simply because according to the definition of the C# language, "up-casting" in this case is boxing.

The language specification for C# contains (in chapter 13) a catalogue of all possible type conversions. All these conversions are categorized in a specific fashion (e.g. 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". Because your struct A is not a class type, these conversions cannot be applied in your example.

    That is, the fact that A is (indirectly) derived from object (while correct) is simply irrelevant here. What is relevant is that A is a struct value type.

  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. Thus every conversion from a struct to object is by definition considered boxing.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文