• 装箱和拆箱认识


    装箱(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工具反汇编程序,可以清楚看到装箱和拆箱。


     


     


     

  • 相关阅读:
    UML类图
    # linux下安装Nodejs环境
    [原创] 如何编写一份不可维护的代码
    [原创作品]观察者模式在Web App的应用
    Thinking In Web [原创作品]
    [原创作品]Javascript内存管理机制
    [小知识] 获取浏览器UA标识
    [原创作品] 对获取多层json值的封装
    Javascript 精髓整理篇之三(数组篇)postby:http://zhutty.cnblogs.com
    [原创作品]一个实用的js倒计时器 postby:zhutty.cnblogs.com
  • 原文地址:https://www.cnblogs.com/ike_li/p/2230222.html
Copyright © 2020-2023  润新知