裝箱:將值類型轉換成引用類型的的一種機制。
拆箱:獲取已裝箱對象中被裝箱字段的地址;值得注意的是拆箱並不是裝箱的逆過程。
如果你不知道怎麼看自己的代碼是不是發生了裝箱,一個簡單的方法就是通過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 }