裝箱和拆箱幾乎是所有面試題中必考之一,看上去簡單,就往往容易被忽視。其實它一點都不簡單的,一個簡單的問題也可以從多個層次來解讀。
常見面試題目:
1.什么是拆箱和裝箱?
2.什么是箱子?
3.箱子放在哪里?
4.裝箱和拆箱有什么性能影響?
5.如何避免隱身裝箱?
6.箱子的基本結構?
7.裝箱的過程?
8.拆箱的過程?
9.下面這段代碼輸出什么?共發生多少次裝箱?多少次拆箱?
int i = 5; object obj = i; IFormattable ftt = i; Console.WriteLine(System.Object.ReferenceEquals(i, obj)); Console.WriteLine(System.Object.ReferenceEquals(i, ftt)); Console.WriteLine(System.Object.ReferenceEquals(ftt, obj)); Console.WriteLine(System.Object.ReferenceEquals(i, (int)obj)); Console.WriteLine(System.Object.ReferenceEquals(i, (int)ftt));
深入淺出裝箱與拆箱
有拆必有裝,有裝必有拆。
在上一文中我們提到,所有值類型都是繼承自System.ValueType,而System.ValueType又是來自何方呢,不難發現System.ValueType繼承自System.Object。因此Object是.NET中的萬物之源,幾乎所有類型都來自她,這是裝箱與拆箱的基礎。
特別注意的是,本文與上一文有直接關聯,需要先了解上一文中值類型與引用類型的原理,才可以更好理解本文的內容。
基本概念
拆箱與裝箱就是值類型與引用類型的轉換,她是值類型和引用類型之間的橋梁,他們可以相互轉換的一個基本前提就是上面所說的:Object是.NET中的萬物之源
先看看一個小小的實例代碼:
int x = 1023; object o = x; //裝箱 int y = (int) o; //拆箱
裝箱:值類型轉換為引用對象,一般是轉換為System.Object類型或值類型實現的接口引用類型;
拆箱:引用類型轉換為值類型,注意,這里的引用類型只能是被裝箱的引用類型對象;
由於值類型和引用類型在內存分配的不同,從內存執行角度看,拆箱與裝箱就勢必存在內存的分配與數據的拷貝等操作,這也是裝箱與拆箱性能影響的根源。
裝箱的過程
int x = 1023; object o = x; //裝箱
裝箱就是把值類型轉換為引用類型,具體過程:
- 1.在堆中申請內存,內存大小為值類型的大小,再加上額外固定空間(引用類型的標配:TypeHandle和同步索引塊);
- 2.將值類型的字段值(x=1023)拷貝新分配的內存中;
- 3.返回新引用對象的地址(給引用變量object o)
如上圖所示,裝箱后內存有兩個對象:一個是值類型變量x,另一個就是新引用對象o。裝箱對應的IL指令為box
,上面裝箱的IL代碼如下圖:
拆箱的過程
int x = 1023; object o = x; //裝箱 int y = (int) o; //拆箱
明白了裝箱,拆箱就是裝箱相反的過程,簡單的說是把裝箱后的引用類型轉換為值類型。具體過程:
- 1.檢查實例對象(object o)是否有效,如是否為null,其裝箱的類型與拆箱的類型(int)是否一致,如檢測不合法,拋出異常;
- 2.指針返回,就是獲取裝箱對象(object o)中值類型字段值的地址;
- 3.字段拷貝,把裝箱對象(object o)中值類型字段值拷貝到棧上,意思就是創建一個新的值類型變量來存儲拆箱后的值;
如上圖所示,拆箱后,得到一個新的值類型變量y,拆箱對應的IL指令為unbox
,拆箱的IL代碼如下:
裝箱與拆箱總結及性能
裝的的什么?拆的又是什么?什么是箱子?
通過上面深入了解了裝箱與拆箱的原理,不難理解,只有值類型可以裝箱,拆的就是裝箱后的引用對象,箱子就是一個存放了值類型字段的引用對象實例,箱子存儲在托管堆上。只有值類型才有裝箱、拆箱兩個狀態,而引用類型一直都在箱子里。
關於性能
之所以關注裝箱與拆箱,主要原因就是他們的性能問題,而且在日常編碼中,經常有裝箱與拆箱的操作,而且這些裝箱與拆箱的操作往往是在不經意時發生。一般來說,裝箱的性能開銷更大,這不難理解,因為引用對象的分配更加復雜,成本也更高,值類型分配在棧上,分配和釋放的效率都很高。裝箱過程是需要創建一個新的引用類型對象實例,拆箱過程需要創建一個值類型字段,開銷更低。
為了盡量避免這種性能損失,盡量使用泛型,在代碼編寫中也盡量避免隱式裝箱。
什么是隱式裝箱?如何避免?
就是不經意的代碼導致多次重復的裝箱操作,看看代碼就好理解了
int x = 100; ArrayList arr = new ArrayList(3); arr.Add(x); arr.Add(x); arr.Add(x);
這段代碼共有多少次裝箱呢?看看Add方法的定義:
再看看IL代碼,可以准確的得到裝箱的次數:
顯示裝箱可以避免隱式裝箱,下面修改后的代碼就只有一次裝箱了。
int x = 100; ArrayList arr = new ArrayList(3); object o = x; arr.Add(o); arr.Add(o); arr.Add(o);
題目答案解析:
1.什么是拆箱和裝箱?
裝箱就是值類型轉換為引用類型,拆箱就是引用類型(被裝箱的對象)轉換為值類型。
2.什么是箱子?
就是引用類型對象。
3.箱子放在哪里?
托管堆上。
4.裝箱和拆箱有什么性能影響?
裝箱和拆箱都涉及到內存的分配和對象的創建,有較大的性能影響。
5.如何避免隱身裝箱?
編碼中,多使用泛型、顯示裝箱。
6.箱子的基本結構?
上面說了,箱子就是一個引用類型對象,因此她的結構,主要包含兩部分:
- 值類型字段值;
- 引用類型的標准配置,引用對象的額外空間:TypeHandle和同步索引塊,關於這兩個概念在本系列后面的文章會深入探討。
7.裝箱的過程?
- 1.在堆中申請內存,內存大小為值類型的大小,再加上額外固定空間(引用類型的標配:TypeHandle和同步索引塊);
- 2.將值類型的字段值(x=1023)拷貝新分配的內存中;
- 3.返回新引用對象的地址(給引用變量object o)
8.拆箱的過程?
- 1.檢查實例對象(object o)是否有效,如是否為null,其裝箱的類型與拆箱的類型(int)是否一致,如檢測不合法,拋出異常;
- 2.指針返回,就是獲取裝箱對象(object o)中值類型字段值的地址;
- 3.字段拷貝,把裝箱對象(object o)中值類型字段值拷貝到棧上,意思就是創建一個新的值類型變量來存儲拆箱后的值;
9.下面這段代碼輸出什么?共發生多少次裝箱?多少次拆箱?
int i = 5; object obj = i; IFormattable ftt = i; Console.WriteLine(System.Object.ReferenceEquals(i, obj)); Console.WriteLine(System.Object.ReferenceEquals(i, ftt)); Console.WriteLine(System.Object.ReferenceEquals(ftt, obj)); Console.WriteLine(System.Object.ReferenceEquals(i, (int)obj)); Console.WriteLine(System.Object.ReferenceEquals(i, (int)ftt));
上面代碼輸出如下,至於發生多少次裝箱多少次拆箱,你猜?
False
False
False
False
False
版權所有,文章來源:http://www.cnblogs.com/anding
個人能力有限,本文內容僅供學習、探討,歡迎指正、交流。
.NET面試題解析(00)-開篇來談談面試 & 系列文章索引
參考資料:
書籍:CLR via C#
書籍:你必須知道的.NET
1.4.2 裝箱和拆箱:http://book.51cto.com/art/201012/237726.htm