參考資料 [1] @只增笑耳Jason的回答 https://www.zhihu.com/question/57208269 [2] 《C 捷徑教程》 疑難解答 1. "裝箱和拆箱是什麼?" 2. "何時發生裝箱與拆箱?" 3. "裝箱與拆箱的效率如何?" 裝箱和拆箱是什麼? 在C 中,裝箱和拆箱發 ...
參考資料
[1] @只增笑耳Jason的回答 https://www.zhihu.com/question/57208269
[2] 《C# 捷徑教程》
疑難解答
裝箱和拆箱是什麼?
在C#中,裝箱和拆箱發生在值類型與引用類型之間。當我們把一個值類型轉換成引用類型時,就發生了裝箱操作。反之,當我們將一個引用類型轉換成值類型時,就發生了拆箱操作。對於值類型和引用類型,感覺《C# 捷徑教程》中講的比較詳盡(當然也可以參考我上一篇寫的關於值類型與引用類型的文章):
你用類來定義對象,用結構來定義值。二者之間存在一個清晰的界限。對象存活在有垃圾回收的記憶體堆上。值通常存活在臨時的存儲空間里,比如棧。前面提到過的一個顯著的例外就是,如果值類型作為一個欄位被包含在一個對象中,那它就可以存活在堆上。它不是自治的,GC不直接控制它的生命周期。
對於裝箱拆箱,知乎上有個答主感覺回答的很精辟:
簡單地講裝箱就是把一個放在stack上的值移動到heap上,拆箱正好相反
如下代碼展示了一個較為典型的裝箱操作:
1 class BoxAndUnBox{
2
3 public static void Main(string[] args) {
4 int a = 1;
5
6 Print(a); // =>在此處值類型a被裝箱為object類型
7 }
8
9 static void Print(object obj) {
10 Console.WriteLine(obj);
11 }
12 }
在第6行處發生了裝箱操作,變數a為值類型int,給a分配的空間在本地棧上。而Print方法接受一個對象引用,這個對象引用是一個指向基於堆的對象的引用。
當我們將值類型傳入該方法時,就發生了裝箱操作,《C# 捷徑教程》對此是這樣描述的:
CLR創建了一個運行時包裝器類來包含這個值類型的副本。包裝器類的實例存活在對上,通常稱為裝箱對象。這是CLR來聯繫值類型和引用類型之間間隔的方法。
其中在第六行發生的操作如下圖所示。
被裝箱後,關鍵的點是箱子內的值是初始值的副本,這意味著我們就算對箱子內的值進行更改,也不會影響到初始值(但並不總是這樣,如果使用介面類型進行裝箱,則修改原始值是可能的)。
何時發生裝箱與拆箱?
根據《C# 捷徑教程》所說,當以下任意轉換髮生時,值類型就被裝箱。
- 從值類型轉換成對對象引用
- 從值類型轉換成System.ValueType引用
- 從值類型轉換成指向值類型實現的介面的引用
- 從枚舉類型轉換成System.Enum引用
第三種情況光看文字描述可能不太清楚,下麵上代碼。
1 public interface IPrint {
2 void Print();
3 }
4
5 public struct Value : IPrint {
6
7 public int x;
8
9 public Value(int x) {
10 this.x = x;
11 }
12
13 public void Print() {
14 Console.WriteLine(x);
15 }
16 }
17
18 class BoxAndUnBox{
19 public static void Main(string[] args) {
20 Value value = new Value(3);
21 IPrint print = value;
22 print.Print();
23 }
24 }
其中在第21行,值類型value被裝箱為IPrint介面類型。這個過程是隱式的,事實上,如果我們直接通過value.Print()來調用方法,則不會發生裝箱。
對於拆箱操作,其發生時機恰好與裝箱操作相反,當以下任意轉換髮生時,引用類型就被拆箱為值類型。
- 從引用類型強制轉回到值類型
- 從System.ValueType類型轉換成值類型
- 從指向值類型實現的介面的引用類型轉換成值類型
- 從System.Enum引用類型轉換成值類型
裝箱與拆箱的效率如何?
先說結論,裝箱是低效的,對於拆箱,CLR的拆箱操作本身並不是低效的,低效的根源在於C#通常把拆箱操作和一個對值複製的操作組合在一起。
在C#中,大部分裝箱操作都是隱式的,舉個例子,當我們將一連串的整型(值類型)插入到ArrayList(非泛型版本)中去時,每插入一次,都是一次裝箱操作。
那麼如何避免裝箱拆箱呢?一個有效的方法是使用泛型,C#中有武裝到牙齒的泛型,對於上述向列表插入的值的方法,完全可以使用List