23

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

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

struct A
{
    public int i;
}

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

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

4

6 回答 6

43

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

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

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

于 2009-12-30T05:47:03.677 回答
19

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

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

显然这两件事完全不同。

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

所以你在那种情况下所做的就是把整数装箱:你创建一个包含整数存储的新对象,然后存储对新对象的引用。

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

于 2009-12-30T07:32:05.033 回答
5

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

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

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

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

于 2009-12-30T05:53:25.810 回答
1

struct是设计上的值类型,因此在转换为引用类型时需要将其装箱。struct派生自System.ValueType,术语派生自System.Object

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

于 2009-12-30T05:50:56.297 回答
0

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

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

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

  1. 存在从类型S到其超类型的隐式类型转换T,但这些只是为“从类类型S到引用类型T的模式定义的。因为您struct A不是类类型,所以这些转换不能应用于您的示例。

    也就是说,A(间接)源自object(虽然正确)的事实在这里根本无关紧要。相关的是A结构值类型。

  2. 与“从值类型到其引用超类型模式匹配的唯一现有转换被归类为装箱转换。因此,根据定义,从 a到的每个转换都被视为装箱。Aobjectstructobject

于 2013-02-23T03:33:56.747 回答
0

回答完问题后,我将介绍与该主题相关的一些“技巧”:

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

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);
于 2009-12-30T09:47:36.543 回答