拳击和拆箱

时间:2010-10-25 14:28:10

标签: c# .net clr

  

可能重复:
  What is boxing and unboxing and what are the trade offs?

好的,我理解当您装箱和取消装箱时会发生什么的基本概念。

Box将值类型(堆栈对象)抛出到System.Object中并将其存储在堆上 Unbox解压缩包含该值类型的堆上的对象,并将其重新放回堆栈,以便可以使用它。

这是我不明白的地方:

  1. 为什么需要这样做......具体的现实世界的例子

  2. 为什么仿制药如此高效?他们说因为Generics不需要拆箱或装箱,好吧..我不明白为什么......在仿制药中背后的东西

  3. 为什么泛型比其他类型更好。可以说比如其他系列吗?

  4. 总而言之,我在现实世界的应用程序中无法理解这一点,然后进一步了解如何使泛型变得更好......为什么它首先不必做任何这样的事情使用泛型时。

8 个答案:

答案 0 :(得分:5)

  1. 只要您想在int变量中保留object,便需要进行拳击。
  2. int的通用集合包含int[]而不是object[]
  3. int放入非通用集合后面的object[],需要您int。 将int放入通用集合后面的int[]不会激活任何拳击。

答案 1 :(得分:3)

首先,堆栈和堆是实现细节。值类型不是通过堆栈定义的。没有什么可说的,堆栈和堆的概念将用于所有能够托管CLR的系统: http://blogs.msdn.com/b/ericlippert/archive/2010/09/30/the-truth-about-value-types.aspx

除此之外:

当装箱值类型时,将读取该值类型中的数据,创建对象,并将数据复制到新对象。 如果你正在装箱集合中的所有物品,这将是很多开销。

如果你有一个值类型的集合并且正在迭代它们,那么每次读取都会发生这种情况,然后这些项目将被取消装箱(与过程相反)只是为了读取值!!

通用集合强烈地类型化为存储在其中的类型,因此不需要进行装箱或拆箱。

答案 2 :(得分:2)

这是取消装箱/装箱部分周围的response

  

我不确定它是如何实现的   单声道,但通用接口将有所帮助   因为编译器会创建一个新的   每种特定类型的功能   使用的不同类型(内部,那里   是一些可以利用的案例   相同的生成函数)。如果一个   具体类型的功能是   生成,没有必要   box / unbox类型。

     

这就是Collections.Generic的原因   库是.NET 2.0的重头戏   因为不再需要收藏品   拳击并变得明显更多   高效。

关于为什么泛型更好,拳击/拆箱范围之外的其他集合是他们也强制类型。您再也不能轻易折腾出可以容纳任何类型的集合。它可以在编译时防止错误,而不是在运行时查看错误。

答案 3 :(得分:2)

MSDN有一篇很好的文章:Boxing and Unboxing (C# Programming Guide)

  

关于简单分配,装箱和拆箱是计算上昂贵的过程。装箱值类型时,必须分配和构造新对象。在较小的程度上,拆箱所需的演员阵容在计算上也很昂贵。

     

Boxing用于在垃圾收集堆中存储值类型。 Boxing是值类型到类型对象或由此值类型实现的任何接口类型的隐式转换。装箱值类型在堆上分配对象实例并将值复制到新对象中。

     

取消装箱是从类型对象到值类型或从接口类型到实现接口的值类型的显式转换。拆箱操作包括:

     
      
  • 检查对象实例以确保它是给定值类型的盒装值。

  •   
  • 将实例中的值复制到value-type变量中。

  •   

同时检查:Exploring C# Boxing

阅读Jeffrey Richter的Type基础知识。他在这里发表了Two sample chapters plus full TOC from Jeffrey Richter's "CLR via C#" (Microsoft Press, 2010)

Jeffrey Richter的书CLR通过C#的一些注释:

  

可以使用名为boxing的机制将值类型转换为引用类型。

     

在内部,这是当一个值类型的实例被装箱时会发生什么:

     
      
  1. 内存是从托管堆分配的。分配的内存量是   值类型的字段所需的大小加上两个额外的开销成员(   托管上的所有对象都需要类型对象指针和同步块索引   堆。

  2.   
  3. 将值类型的字段复制到新分配的堆内存中。

  4.   
  5. 返回对象的地址。该地址现在是对象的引用;值类型现在是引用类型。 C#编译器自动生成封装值类型实例所需的IL代码,但您仍需要了解内部发生的事情,以便了解代码大小和性能问题。

  6.   

  

请注意。应该注意的是,FCL现在包括一组新的泛型集合类,这些类使得非泛型集合类过时。例如,您应该使用System.Collections.Generic.List类而不是System.Collections.ArrayList   类。泛型集合类相对于非泛型等价物提供了许多改进。例如,API已经过清理和改进,集合类的性能也得到了很大提高。但最大的改进之一是泛型集合类允许您使用值类型的集合,而不需要将集合中的项目装箱/取消装箱。这本身可以极大地提高性能,因为在托管堆上创建的对象要少得多,从而减少了应用程序所需的垃圾回收数量。此外,您将获得编译时类型安全性,并且由于更少的强制转换,您的源代码将更加清晰。这将在第12章中进一步详细解释,   “泛型。”


我不想在这里过分引用完整章节。阅读他的书,你获得了一些关于过程的细节并得到了一些答案。顺便说一句,在这里,在网络和许多书籍中回答你的问题。这是你必须要了解的基础知识。

答案 4 :(得分:1)

以下是Eric Lippert的一篇有趣的读物(关于价值类型的真相): http://blogs.msdn.com/b/ericlippert/archive/2010/09/30/the-truth-about-value-types.aspx

关于你的陈述:

  

Box将值类型(堆栈对象)抛出到System.Object中并将其存储在堆Unbox解包该堆上的对象并保存该值类型并将其重新放回堆栈以便可以使用它。

答案 5 :(得分:0)

简单来说,装箱和拆箱需要很多时间。为什么 - 因为从一开始就使用已知类型然后让这个句柄用于运行时更快。

  1. 在对象的集合中可以包含不同的项:string,int,double等,并且每次使用变量的操作都是corect时必须检查。
  2. 从一种类型转换为另一种需要时间。
  3. 通用更快,并鼓励您使用它们,旧的集合存在以实现向后兼容性

答案 6 :(得分:0)

  1. 这需要完成,因为在IL级别,值类型的指令与引用类型(ldfld vs ldflda不同,请检查disassembly以获取调用{{{ 1}} vs someValueType.ToString(),您会看到说明不同)。

    这些指令不兼容,因此,当您需要将值类型作为对象传递给方法时,该值需要包装在引用类型(装箱)中。这是无效的,因为运行时需要复制值类型,然后创建一个新的装箱类型以传递一个值。

  2. 泛型更快,因为值类型可以存储为值而不是引用,因此不需要装箱。取someReferenceType.ToString() vs ArrayList。如果您想将List<int>放入1,则CLR需要将ArrayList打包,以便将其存储在int中。但是,object[]使用List<T>来存储列表内容,因此List使用T[],这意味着int[]不需要装箱,以便将其放入阵列

答案 7 :(得分:0)

假设我想在List中存储一堆Long类型的变量,但系统既不支持值类型泛型也不支持装箱。存储这些值的方法是定义一个新类“BoxedLong”,它保存一个Long类型的单个字段“Value”。然后,要向列表中添加值,可以创建BoxedLong的新实例,将其Value字段设置为所需的值,并将其存储在列表中。要从列表中检索值,可以从列表中检索BoxedLong对象,并从其Value字段中获取值。

当一个值类型被传递给期望一个Object的东西时,除了没有新的标识符名称之外,上面的内容基本上是在幕后发生的。

将泛型与值类型一起使用时,系统不使用值持有者类并将其传递给期望使用对象的例程。相反,系统会创建一个新版本的例程,该版本将使用相关的值类型。如果将五种不同的值类型传递给通用例程,则将生成五个不同版本的例程。通常,这将产生比使用值持有者类更多的代码,但是每次传入或检索值时代码都必须做更少的工作。由于大多数例程都会传入或传出每种类型的许多值,因此通过消除装箱/拆箱操作可以获得生成不同版本例程的成本。