看下面一段代码:
class Program { static void Main(string[] args) { ArrayList array = new ArrayList(); Point p;//分派一个 for (int i = 0; i < 5; i++) { p.x = i;//初始化值 p.y = i; array.Add(p);//装箱 } } } public struct Point { public Int32 x; public Int32 y; }
轮回5次,每次都初始化一个Point值范例字段,然后放到ArrayList中。Struct是一个值范例的构造,那末ArrayList中存的什么呢?我们再看一下ArrayList的Add要领。MSDN中能够看到Add要领:
public virtual int Add(Object value),
能够看出Add的参数是Object范例,也就是它须要的参数是一个对象的援用。也就是说这里的参数必需是援用范例。至于作甚援用范例,就没必要细说了,不过就是堆上的一个对象的援用。不过在这里为了轻易明白,再次说一下堆和栈。
1、栈区(stack)— 由编译器自动分派开释 ,寄存函数的参数值,局部变量的值等。
2、堆区(heap)— 由顺序员分派开释, 若顺序员不开释,顺序结束时能够由OS接纳。
比方下面:
class Program { static void Main(string[] args) { Int32 n;//这是值范例,寄存在栈中,Int32初始值为0 A a;//此时在栈中拓荒了空间 a = new A();//真正实例化后的一个对象则保留在堆中。 } } public class A { public A() { } }
再回到上面的题目中,Add要领须要援用范例的参数,怎么办呢?那就要用到装箱,所谓装箱,就是将一个值范例转换为一个援用范例。转换的历程是如许的:
1、在托管堆中分派好内存。分派的内存量是值范例的各个字段须要的内存量加上托管堆的一切对象都有的两个分外成员(范例对象指针和同步块索引)须要的内存量。
2、值范例的字段复制到新分派的对内存。
3、返回对象的地点。此时,这个地点是对一个对象的援用,值范例如今已转换为了一个援用范例。
如许,在Add要领中,保留的是一个被装箱的Point对象的援用。装箱后的这个对象会一直在堆中,晓得顺序员处置惩罚或许体系垃圾接纳。这时刻,已装箱的值范例的生存周期超过了未装箱的值范例的生存周期。
有了上面的装箱,天然就须要拆箱了,假如要掏出array的第0个:
Point p = (Point)array[0];
这里要做的是,猎取ArrayList的元素0的援用,将其放到Point值范例p中。为了到达这个目标,怎样完成呢,起首,猎取已装箱的Point对象的各个Point字段的地点。这就是拆箱。然后,将这些字段包括的值从堆中复制到基于栈的值范例实例中。拆箱实在就是猎取一个援用的历程,该援用指向包括在一个对象中的原始值范例。事实上,援用指向的是已装箱实例中的未装箱部份。因而和装箱差别,拆箱不须要在内存中复制任何字节。不过另有一点,拆箱后紧接着发作一次字段的复制操纵。
所以装箱和拆箱会对顺序的速率和内存斲丧形成不利影响,所以要注意什么时刻顺序会自动举行装箱/拆箱操纵,在写代码时要只管防止这些状况。
拆箱时,要注意下面的非常:
1、假如包括了“对已装箱值范例实例的援用”的变量为null,会抛出NullReferenceException。
2、假如援用指向的对象不是所期待的值范例的已装箱实例,会抛出InvalidCastException。
比方以下代码片断:
Int32 x = 5; Object o = x; Int16 r = (Int16)o;//抛出InvalidCastException非常
因为拆箱时刻只能将其转换为本来未装箱时的值范例。对上述代码修正成:
Int32 x = 5; Object o = x; //Int16 r = (Int16)o;//抛出InvalidCastException非常 Int16 r = (Int16)(Int32)o;
此时准确。
在拆箱后,会发作一次字段复制,以下代码:
//会发作字段复制 Point p1; p1.x = 1; p1.y = 2; Object o = p1;//装箱,发作复制 p1 = (Point)o;//拆箱,并将字段从已装箱的实例复制到栈中
再看以下代码段:
//要转变已装箱的值 Point p2; p2.x = 10; p2.y = 20; Object o = p2;//装箱 p2 = (Point)o;//拆箱 p2.x = 40;//转变栈中变量的值 o = p2;//再一次装箱,o援用新的已装箱实例
这里的目标是要将装箱后的p2的x值改成40,如许,就须要先拆一次箱,实行一次复制字段到栈中,在栈中转变字段的值,然后实行一次装箱,这时刻又要在堆上建立一个全新的已装箱实例。由此也我们也看到装箱/拆箱和复制对顺序机能的影响。
下面再看几个装箱拆箱的代码段:
//装箱拆箱演示 Int32 v = 5; Object o = v; v = 123; Console.WriteLine(v + "," + (Int32)o);
这里发作了3次装箱,可显著看出的是
Object o = v; v = 123;
但是在Console.WriteLine里还发作了一次装箱,为何呢?因为这里的WriteLine中是string范例的参数,而string人人都晓得是援用范例的,所以(Int32)o在这里还要举行一次装箱。在这里再次说清楚明了在顺序中运用+号衔接字符串的题目,衔接的时刻有几个值范例,那末就要举行频频装箱操纵。
不过,上述代码能够修正:
//修正后 Console.WriteLine(v.ToString() + "," + o);
如许就没有装箱了。
再看以下代码:
Int32 v = 5; Object o = v; v = 123; Console.WriteLine(v); v = (Int32)o; Console.WriteLine(v);
这里只发作了一次装箱,即Object o = v这里,而Console.WriteLine因为重载了int,bool,double等,所以这里并不发作装箱。
以上就是C#基础知识整顿 基础知识(18) 值范例的装箱和拆箱(一)的内容,更多相关内容请关注ki4网(www.ki4.cn)!