10

在某些情况下,需要将值类型的实例视为引用类型的实例。对于这种情况,可以通过一个称为装箱的过程将值类型实例转换为引用类型实例。当一个值类型实例被装箱时,存储在堆上分配并且实例的值被复制到那个空间中。对此存储的引用被放置在堆栈上。装箱的值是一个对象,一个包含值类型实例内容的引用类型。

了解 .NET 的通用类型系统

Wikipedia中有一个 Java 示例。但是在 C# 中,在哪些情况下必须将值类型装箱?或者一个更好/类似的问题是,为什么要将值类型存储在堆(装箱)而不是堆栈上?

4

9 回答 9

14

通常,您通常希望避免对值类型进行装箱。

但是,在极少数情况下这很有用。例如,如果您需要以 1.1 框架为目标,您将无法访问通用集合。在 .NET 1.1 中对集合的任何使用都需要将您的值类型视为 System.Object,这会导致装箱/拆箱。

这在 .NET 2.0+ 中仍有一些用处。任何时候您想利用所有类型(包括值类型)都可以直接视为对象的事实,您可能需要使用装箱/拆箱。这有时会很方便,因为它允许您在集合中保存任何类型(通过在泛型集合中使用 object 而不是 T),但通常最好避免这种情况,因为您会失去类型安全性。但是,经常发生装箱的一种情况是当您使用反射时 - 在使用值类型时,反射中的许多调用都需要装箱/拆箱,因为事先不知道类型。

于 2009-06-22T17:45:07.507 回答
13

几乎没有充分的理由故意装箱一个值类型。几乎总是,将值类型装箱的原因是将其存储在某个不知道类型的集合中。例如,旧的ArrayList是对象的集合,它们是引用类型。例如,收集整数的唯一方法是将它们装箱为对象并将它们传递给 ArrayList。

现在,我们有通用集合,所以这不是一个问题。

于 2009-06-22T17:43:54.407 回答
9

装箱通常在必要时在 .NET 中自动发生;通常当您将值类型传递给需要引用类型的东西时。一个常见的例子是 string.Format()。当您将原始值类型传递给此方法时,它们将作为调用的一部分装箱。所以:

int x = 10;
string s = string.Format( "The value of x is {0}", x ); // x is boxed here

这说明了一个简单的场景,其中值类型 (x) 被自动装箱以传递给需要对象的方法。通常,您希望尽可能避免装箱值类型......但在某些情况下它非常有用。

有趣的是,当您在 .NET 中使用泛型时,值类型在用作类型的参数或成员时不会被装箱。这使得泛型比将所有内容视为 {object} 以与类型无关的旧 C# 代码(例如 ArrayList)更有效。这又增加了一个使用泛型集合的理由,例如List<T>Dictionary<T,K>超过ArrayListHashtable

于 2009-06-22T17:53:23.627 回答
5

我会向您推荐 Eric Lippert 的 2 篇不错的文章

http://blogs.msdn.com/ericlippert/archive/2009/04/27/the-stack-is-an-implementation-detail.aspx

http://blogs.msdn.com/ericlippert/archive/2009/05/04/the-stack-is-an-implementation-detail-part-two.aspx

这是我100%同意的报价

将堆栈用于值类型的局部变量只是 CLR 代表您执行的优化。值类型的相关特征是它们具有按值复制的语义,而不是有时它们的释放可以由运行时优化。

在 99% 的应用程序中,开发人员不应该关心为什么值类型在堆栈中而不是在堆中,以及我们在这里可以获得什么性能提升。Juts 有非常简单的规则:

  1. 不必要时避免装箱/拆箱,使用泛型集合。大多数问题不是在您定义自己的类型时发生,而是在您不正确地使用现有类型(由 Microsoft 或您的同事定义)时发生
  2. 使您的值类型简单。如果您需要一个具有 10-20 个字段的结构,我想您最好创建一个类。想象一下,每次你偶尔按值传递一个函数时,所有这些字段都会被复制......
  3. 我认为在内部包含引用类型字段的值类型不是很有用。就像带有字符串和对象字段的结构一样。
  4. 根据所需的功能定义您需要的类型,而不是应该存储的位置。与类相比,结构的功能有限,因此如果结构不能提供所需的功能,如默认构造函数,请定义类。
  5. 如果某物可以对其他类型的数据执行任何操作,则通常将其定义为一个类。只有当您可以将一种类型转换为另一种类型时,才应定义具有不同类型的结构操作。假设您可以将 int 添加到 double,因为您可以将 int 转换为 double。
  6. 如果某些东西应该是无状态的,那么它就是一个类。
  7. 当您犹豫时,请使用引用类型。:-)

任何规则都允许在特殊情况下排除,但不要试图过度优化。

ps 我遇到了一些 2-3 年经验的 ASP.NET 开发人员,他们不知道堆栈和堆之间的区别。:-( 如果我是面试官,我不会雇用这样的人,但不是因为装箱/拆箱可能是我见过的任何 ASP.NET 网站的瓶颈。

于 2009-06-22T18:49:54.173 回答
2

我认为 c# 中的一个很好的装箱示例出现在像ArrayList这样的非泛型集合中。

于 2009-06-22T17:44:39.413 回答
1

一个例子是当一个方法接受一个对象参数并且必须传入一个值类型时。

于 2009-06-22T17:45:44.077 回答
1

下面是一些装箱/拆箱的例子

ArrayList ints = new ArrayList();
myInts.Add(1); // boxing
myInts.Add(2); // boxing

int myInt = (int)ints [0]; // unboxing

Console.Write("Value is {0}", myInt); // boxing
于 2009-06-22T17:46:35.383 回答
1

发生这种情况的一种情况是,例如,如果您有期望类型为 object 的参数的方法,并且您正在传入其中一种原始类型,例如 int。或者,如果您将参数定义为 int 类型的“ref”。

于 2009-06-22T17:47:08.973 回答
1

编码

int x = 42;
Console.Writeline("The value of x is {0}", x );

实际上是装箱和拆箱,因为里面Writeline有一个int演员。为避免这种情况,您可以这样做

int x = 42;
Console.Writeline("The value of x is {0}", x.ToString());

当心微妙的错误!

您可以通过将自己的类型声明为 来声明自己的值类型struct。想象一下,您声明 astruct有很多属性,然后将一些实例放入ArrayList. 这当然是把它们装箱了。现在通过运算符引用一个[],将其转换为类型并设置一个属性。您只需在副本上设置一个属性。中的那个ArrayList仍然未修改。

出于这个原因,值类型必须始终是不可变的,即使所有成员变量readonly只能在构造函数中设置,并且没有任何可变类型作为成员。

于 2009-06-22T19:21:45.053 回答