zoukankan      html  css  js  c++  java
  • 装箱和拆箱认识

    装箱(Boxing):值类型->引用类型,把一个值类型数据放到堆上,就需要装箱操作.

    拆箱(Unboxing):引用类型->值类型,把一个放在堆上的值类型数据取出来,则需要进行拆箱操作.

    值类型(原类型(SbyteByteShortUshortIntUintLongUlongCharFloatDoubleBoolDecimal)、枚举(enum)、结构(struct);

    值类型数据是分配在栈中.

    引用类型(类,接口,数组,委托,字符串等);引用类型数据分配在堆上.

    例如,对于如下简单的装箱和拆箱操作语句。

    int i = 123;
    object obj = i;//Boxing
    if( obj is int )
    int j = (int) obj;//Unboxing

     装箱和拆箱存在的意义:值类型是数据的容器,它存储在堆栈上,不具备多态性,而.NET框架在整个对象层次的设计中,使用System.Object作为所有类型的基类,但是Obejct是引用类型,而作为值类型的基类System.ValueType,是从System.Object派生出来的,这就产生了矛盾,装箱和拆箱就是为了解决这两种类型之间的差异。

        装箱会将一个值类型放入一个未具名类型(untyped)的引用对象中,从而允许该值类型应用于那些只能使用引用类型的场合。拆箱则会从前面的装箱对象中提取出一个值类型的副本。装箱和拆箱都是比较耗时的操作。

        装箱操作会将值类型转换为一个引用类型,这个过程中会创建一个新的引用独享,然后将其分配到堆上,同时值类型的副本会被存储在该引用对象内部。当我们需要从装箱对象中获取任何信息时,会创建值类型的一个副本,然后将其返回。其中的关键是:当我们需要引用类型时,会创建一个新的引用类型的对象并将其放入到堆中;当我们需要访问已经装箱的对象信息时,就会创建对应值类型的一个副本,并将其返回。

        装箱和拆箱最大的问题是它们会自动发生。当我们使用的是值类型,而期望的是引用类型,那么编译器就会自动产生装箱和拆箱语句。

       我们来看下面的语句,居然也发生了装箱和拆箱操作。

    Console.WriteLine("A few numbers:{0}, {1}, {2}",
    25, 32, 50);

        上述代码之所以发生了装箱,是因为WriteLine方法需要的参数类型是System.Object,而25是一个int类型,属于值类型,因此需要装箱,而在WriteLine方法内部实现时,需要调用方法参数的ToString()方法,为了调用装箱对象的方法,就会发生拆箱的操作。

        为了避免装箱和拆箱,可以将上述代码进行如下修改。

    Console.WriteLine("A few numbers:{0}, {1}, {2}",
    25.ToString(), 32.ToString(), 50.ToString());

        另外,由于装箱和拆箱都会产生新的实例,那么有时会产生一些诡异的bug,我们来查看下面的代码。

    代码
    1 publicstruct Person
    2 {
    3 privatestring _Name;
    4
    5 publicstring Name
    6 {
    7 get
    8 {
    9 return _Name;
    10 }
    11 set
    12 {
    13 _Name = value;
    14 }
    15 }
    16
    17 publicoverridestring ToString( )
    18 {
    19 Return _Name;
    20 }
    21 }
    22
    23  // Using the Person in a collection:
    24 ArrayList attendees =new ArrayList( );
    25 Person p =new Person( "Old Name" );
    26 attendees.Add( p );
    27
    28 // Try to change the name:
    29 // Would work if Person was a reference type.
    30 Person p2 = (( Person )attendees[ 0 ] );
    31 p2.Name ="New Name";
    32
    33 // Writes "Old Name":
    34 Console.WriteLine(
    35 attendees[ 0 ].ToString( ));

        上述代码中,Person是一个值类型,在将其放入ArrayList时,会进行装箱操作,这时会有一次复制操作,当我们需要获得ArrayList内Person对象的信息时,需要一次拆箱,又会有一次复制操作,因此,当我们并没有对ArrayList内的对象进行修改,而是针对副本进行修改。

        我们可以通过以下的方式来修改上述代码存在的问题。

    代码
    1 publicinterface IPersonName
    2 {
    3 string Name
    4 {
    5 get; set;
    6 }
    7 }
    8
    9 struct Person : IPersonName
    10 {
    11 privatestring _Name;
    12
    13 publicstring Name
    14 {
    15 get
    16 {
    17 return _Name;
    18 }
    19 set
    20 {
    21 _Name = value;
    22 }
    23 }
    24
    25 publicoverridestring ToString( )
    26 {
    27 return _Name;
    28 }
    29 }
    30
    31 // Using the Person in a collection:
    32 ArrayList attendees =new ArrayList( );
    33 Person p =new Person( "Old Name" );
    34 attendees.Add( p ); // box
    35
    36 // Try to change the name:
    37 // Use the interface, not the type.
    38 // No Unbox needed
    39 (( IPersonName )attendees[ 0 ] ).Name ="New Name";
    40
    41 // Writes "New Name":
    42 Console.WriteLine(
    43 attendees[ 0 ].ToString( )); // unbox
    44
    45

        装箱后的引用类型实现了原来值类型对象上所有的接口,这意味着不会再发生复制,但是当我们调用IPersonName.Name属性时,它会将调用请求转发给“箱子”内部的值类型,在值类型上实现接口使我们可以访问”箱子“的内部,从而允许直接改变ArrayList中的信息。

       总之,我们应该对任何将值类型转换为System.Object或者接口类型的构造保持密切的关注,例如将值类型放入集合中,在值类型上调用System.Object定义的方法等,这些操作都会将值类型转换为System.Object,只要有可能,我们都应该避免这种转换。

     使用微软自带ildasm.exe工具反汇编程序,可以清楚看到装箱和拆箱。


     


     


     

  • 相关阅读:
    JavaScript对原始数据类型的拆装箱操作
    Javascript继承(原始写法,非es6 class)
    动态作用域与词法作用域
    自行车的保养
    探索JS引擎工作原理 (转)
    C语言提高 (7) 第七天 回调函数 预处理函数DEBUG 动态链接库
    C语言提高 (6) 第六天 文件(续) 链表的操作
    C语言提高 (5) 第五天 结构体,结构体对齐 文件
    C语言提高 (4) 第四天 数组与数组作为参数时的数组指针
    C语言提高 (3) 第三天 二级指针的三种模型 栈上指针数组、栈上二维数组、堆上开辟空间
  • 原文地址:https://www.cnblogs.com/ike_li/p/2230222.html
Copyright © 2011-2022 走看看