天天看点

装箱与拆箱装箱与拆箱

装箱与拆箱

装箱

装箱其实就是值类型向引用类型转化的过程。将一个值类型变量装箱成一个引用类型变量,首先会在托管堆上为新的引用类型变量分配内存空间,然后将值类型变量拷贝到托管堆上新分配的对象内存中,最后返回新分配的对象内存地址。

拆箱

拆箱操作获取指向对象中包含值类型部分的指针,然后由程序员手动将其对应的值拷贝给值类型变量。
class Main
{
    static void main()
    {
        int age = new int();

        object obj = age;

        //age = null;

        age = (int)obj;

        Console.WriteLine("age:{0}", age);
    }
}
           

首先声明了一个值类型age,然后声明一个引用类型obj,然后把age赋值给它,这便是一次装箱操作。编译器先在托管堆上分配一块内存空间(空间大小为对象中包含的值类型变量所占空间总和外加一个方法表指针和一个SyncBlockIndex),然后把age拷贝到这个空间中,再返回该空间的引用地址。接下来则是拆箱操作,编译器获取到obj对象中值类型变量的指针,然后将其值拷贝给值类型变量。如果把注释掉的代码打开,那么就会抛出System.NullReferenceException异常,要说问什么,这又会牵扯出值类型跟引用类型另一个大的不同。看见了吧,声明age时并没有赋值,如果关掉注释代码,程序不会报错,最后打印出个0,这说明在声明值类型变量时,如果没有初始化赋值,编译器会自动将其赋值为0,既然值类型没有引用,那么它就不可能为空。引用类型不一样,它可以为空引用,一张过期作废的银行卡是可以存在。而如果将一个空的对象拆箱,编译器上哪儿去找它里面的值类型变量的指针呢?所以这也是拆箱操作需要注意的地方。

  • 所有值类型都继承自System.ValueType,但是ValueType没有附加System.Object包含之外其它任何方法,不过它倒是改写了Equals和GetHashCode两个方法。引用类型变量的Equals比较的是二者的引用地址而不是内部的值,值类型变量的Equals方法比较的是二者的值而不是……哦对了,值类型压根儿没有引用地址;
  • 值类型不能作为其它任何类型的基类型,因此不能向值类型中增加任何新的虚方法,更不该有任何抽象方法,所有的方法都是sealed的(不可重写);
  • 未装箱的值类型分配在栈上而不是堆上,而栈又不是GC的地盘儿,因此GC根本不过问值类型变量的死活,一旦值类型变量的作用范围一过,它所占的内存空间就立即被回收掉,不劳GC亲自动手。