声明一个值类型变量,编译器会在栈上分配一个空间,这个空间对应着该值类型变量,空间里存储的就是该变量的值。
引用类型的实力分配在堆上,新建一个引用类型实例,得到的变量对应的是该实例的内存分配地址,这就像您的银行账号一样。
1: public static class BoxingAndUnboxing
2: {
3: public static void Demonstration()
4: {
5: int ageInt = new int();
6:
7: // Boxing operation.
8: object ageObject = ageInt;
9:
10: //ageObject = null;
11:
12: // Unboxing operation.
13: ageInt = (int)ageObject;
14:
15: Console.WriteLine(ageInt);
16: }
17: }
在该方法中,我们首先声明了一个值类型变量ageint,但并未给它赋值,接着声明了一个典型的引用类型变量ageobject,并把ageint赋给它,这里就进行了一次装箱操作。编译器现在托管堆上分配一块内存空间(空间大小为对象中包含的值类型变量所占空间总和外加一个方法表指针和一个syncblockindex),然后把ageint拷贝到这个空间中,再返回该空间的引用地址,接下来第13行是拆箱操作,编译器获取到ageobject对象中值类型变量的指针,然后将其值拷贝给值类型变量。如果你把第10行的代码打开,程序不会报错,最后打印出个0,这说明在声明值类型变量时,如果没有初始化赋值,编译器会自动将其赋值为0,既然值类型没有引用,那么它就不可能为空。引用类型不一样,它可以为空引用,一张过期作废的银行卡可以存在。而如果将一个空的对象装箱,编译器上哪儿区找它里面的值类型变量的指针呢?所以这也是拆箱操作需要注意的地方。
未装箱的值类型分配在栈上而不是堆上,而栈又不是GC的地盘儿,因此GC根本不过问值类型变量的死活,一旦值类型变量的作用范围一过,它所占的空间就立即被回收掉,不劳GC亲自动手。