装箱:将值类型转换成引用类型的的一种机制。
拆箱:获取已装箱对象中被装箱字段的地址;值得注意的是拆箱并不是装箱的逆过程。
如果你不知道怎么看自己的代码是不是发生了装箱,一个简单的方法就是通过visual studio自带的IL反编译工具查看,
如下面的代码,大家可以在IL指令中去找box,如果哪里出现它,就说明这里发生了装箱
当然,上面的代码大家一般不会这么写,这里只是告诉大家怎么去自己的代码你去找装箱发生的地方。
我把上面的代码注释了,重写了一行代码,重新打开IL反编译工具,大家看看有什么不同?
这次为啥没有box了呢?因为代码里没发生装箱。为啥呢?因为Console.WriteLine()方法有重载的方法太多了,其中方法签名中就有包含Int32的,所以写代码的时候一定要注意参数的类型。许多看似差不多的代码,背后往往暗藏玄机。
那么怎么知道代码中有没有发生拆箱呢?
同样的可以在IL指令中找到unbox
现在大家都知道代码中有没有发生装/拆箱了,那装/拆箱的过程中都干了哪些事呢?
1 static void Main(string[] args) 2 { 3 Int32 number=5; 4 Object o = number;//装箱 5 number = 10; 6 Console.WriteLine(number); 7 number = (Int32)o;//拆箱 8 Console.WriteLine(number); 9 }
装箱:
1、在堆中开辟一片空间,这片空间的大小是number的值所占内存的大小加上托管堆对象的两个额外成员(类型对象指针和同步块索引)。
2、将number的值复制到刚才分配的空间中。
3、返回堆中这个对象的对象的引用地址。
然后对象o就拿到了这个地址,当然这时候已经完成了装箱。
从这个过程中,可以看出,只要发生装箱,就需要去堆内存中开辟空间,再把字段赋值过去,这就是为什么装箱会造成内存消耗,影响性能的原因,所以应该在程序中尽可能的避免写发生装箱的代码。当然该写的时候就得写。
拆箱:
在文章开头的时候其实已经说明了拆箱的过程,拆箱就是在堆中找到原始值类型的地址的过程。也就是说它没有进行值的复制,所以相对来说比装箱消耗性能要小得多了。
拆箱的时候有一个知识点也是需要注意的,就是你把什么样的值类型装箱了拆的时候需要把他拆成什么样的类型,否则就会发生InvalidCastException异常。
最后,你能看出下面的代码发生了几次装箱了么?不注意的话,好多码崽经常会写出下面的代码哦
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 Point p = new Point(2, 5); 6 Console.WriteLine(p); 7 Console.WriteLine("{0};{1}", p, p); 8 Console.WriteLine(p.ToString()); 9 Console.WriteLine(p.GetType()); 10 } 11 } 12 13 struct Point 14 { 15 readonly Int32 x; 16 readonly Int32 y; 17 public Point(Int32 x, Int32 y) 18 { 19 this.x = x; 20 this.y = y; 21 } 22 public override string ToString() 23 { 24 return string.Format("{0}-{1}", x, y); 25 } 26 }