程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#基礎知識 >> .NET面試題解析(02)-拆箱與裝箱

.NET面試題解析(02)-拆箱與裝箱

編輯:C#基礎知識
  系列文章目錄地址: .NET面試題解析(00)-開篇來談談面試 & 系列文章索引

裝箱和拆箱幾乎是所有面試題中必考之一,看上去簡單,就往往容易被忽視。其實它一點都不簡單的,一個簡單的問題也可以從多個層次來解讀。

  常見面試題目:

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)

image

如上圖所示,裝箱後內存有兩個對象:一個是值類型變量x,另一個就是新引用對象o。裝箱對應的IL指令為box,上面裝箱的IL代碼如下圖:

image

大笑 拆箱的過程

int x = 1023;
object o = x; //裝箱
int y = (int) o; //拆箱

明白了裝箱,拆箱就是裝箱相反的過程,簡單的說是把裝箱後的引用類型轉換為值類型。具體過程:

  • 1.檢查實例對象(object o)是否有效,如是否為null,其裝箱的類型與拆箱的類型(int)是否一致,如檢測不合法,拋出異常;
  • 2.指針返回,就是獲取裝箱對象(object o)中值類型字段值的地址;
  • 3.字段拷貝,把裝箱對象(object o)中值類型字段值拷貝到棧上,意思就是創建一個新的值類型變量來存儲拆箱後的值;

image

如上圖所示,拆箱後,得到一個新的值類型變量y,拆箱對應的IL指令為unbox,拆箱的IL代碼如下:

image 

吐舌笑臉 裝箱與拆箱總結及性能

裝的的什麼?拆的又是什麼?什麼是箱子?

通過上面深入了解了裝箱與拆箱的原理,不難理解,只有值類型可以裝箱,拆的就是裝箱後的引用對象,箱子就是一個存放了值類型字段的引用對象實例,箱子存儲在托管堆上。只有值類型才有裝箱、拆箱兩個狀態,而引用類型一直都在箱子裡

關於性能

之所以關注裝箱與拆箱,主要原因就是他們的性能問題,而且在日常編碼中,經常有裝箱與拆箱的操作,而且這些裝箱與拆箱的操作往往是在不經意時發生。一般來說,裝箱的性能開銷更大,這不難理解,因為引用對象的分配更加復雜,成本也更高,值類型分配在棧上,分配和釋放的效率都很高。裝箱過程是需要創建一個新的引用類型對象實例,拆箱過程需要創建一個值類型字段,開銷更低。

為了盡量避免這種性能損失,盡量使用泛型,在代碼編寫中也盡量避免隱式裝箱。

什麼是隱式裝箱?如何避免?

就是不經意的代碼導致多次重復的裝箱操作,看看代碼就好理解了

int x = 100;
ArrayList arr = new ArrayList(3);
arr.Add(x);
arr.Add(x);
arr.Add(x);

這段代碼共有多少次裝箱呢?看看Add方法的定義:

image

再看看IL代碼,可以准確的得到裝箱的次數:

image

顯示裝箱可以避免隱式裝箱,下面修改後的代碼就只有一次裝箱了。

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://hovertree.com

個人能力有限,本文內容僅供學習、探討,歡迎指正、交流。

  參考資料:

書籍:CLR via C#

書籍:你必須知道的.NET

1.4.2 裝箱和拆箱

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved