1
2
3
4
5
參考型別/引用類型
實值型別/值類型
Stack(棧/堆疊)
Heap(堆/堆積)
內存/記憶體/RAM
在學習裝箱vs拆箱之前,首先要先了解一下基本概念。
在C#中,所有的數據類型都會被分為兩種:「值類型」和「引用類型」。
值類型 vs 引用類型
程式在執行過程中,會把內存劃分為若干個區塊,最常見到的,也最重要的兩個區塊就是:「Stack(棧/堆疊)」和「Heap(堆/堆積)」。
- 值類型
Value Type - 引用類型
Reference Type - 內存:
Stack(棧/堆疊) 與Heap(堆/堆積)
值類型 Value Type
因為Stack的內存空間有限,由編譯器自動分配,用來保存最簡單、最基本的數據類型,我們的「值類型」數據就是保存在這裡。
值類型數據包括:byte, int, float, char, bool,以及結構struct,這些簡單的、長度有限的數據類型。
保存在 Stack 中的數據,生命周期一般都比較短,在程序運行中,一旦保存在 Stack中的數據使用完畢,則立刻會被編譯器自動清理、自動回收。
- 值類型保存在
Stack(棧/堆疊)內 - 值類型數據:
byte,int,float,char,bool,以及結構struct - 內存由編譯器自動分配,程序結束內存會自動被回收
引用類型 Reference Type
- 引用類型保存在
Heap(堆/堆積) 內存中
為什麼要區分「值類型」和「引用類型」?
1
2
3
4
var arr = new ArrayList()
arr.Add(1);
arr.Add("一二三");
arr.Add(car);
1是「值類型」數據,保存在Stack中- 執行
Add(1) 整數1被隱式轉化為對象1(物件1)- 值類型轉化為「引用類型」
- 保存在
Heap內存中
裝箱 Boxing
- 一個「值類型」轉化為「引用類型」的過程,我們就把它稱為「裝箱 Boxing」
- 基本類型變為「對象(物件)類型」
- 從「值類型」變為「引用類型」
- 保存位置,也會從
stack轉移到heap中 - 「裝箱」就是把一個基本數據使用對象(物件)包裝起來,就好像裝箱子的過程
1
2
3
4
int i = 10; //編譯器會在 stack 中保存一個整數為10的數據
object o = i; //接著裝箱開始了,整數10被裝進一個對象(物件)中(保存在`heap`內存中)
//or
object o2 = 10;
編譯器會在 stack 中保存一個整數為10的數據,
接著裝箱開始了,整數10被裝進一個對象(物件)中,
而程序中,任何一個對象(物件)都屬於「引用類型」,保存在heap內存中,
然後,編譯器會在stack 中創建一個變量,來引導heap 中數據所保存的內存地址,stack 中的變量引用 heap 中數據,這也就是「引用類型」 這個名詞的來源。
由此可見,經過「裝箱」操作以後,數據會從普通的基本類型,轉化為對象(物件),會從「值類型」轉化變為「引用類型」,而數據的保存位置,也會從 stack 轉移到 heap 中。
所以說,「裝箱」就是把一個基本數據使用對象(物件)包裝起來,就好像裝箱子的過程。
拆箱 Unboxing
而「拆箱」則是一個完全相反的過程,就是把基本數據類型,從對象(物件)中打開提取出來,
- 從對象(物件)中打開,提取基本數據類型
1
2
object obj = 10;
int number = (int)obj;
首先聲明一個對象,數據類型給它賦值為10,然後我們把這個對象轉化為整數類型的時候,「拆箱」就發生了。
當一個對象類型轉化為基本數據類的時候,這個數據,就會從內存heap 中移轉到 stack中。
注意
不管是「裝箱」還是「拆箱」,都會有比較明顯的性能問題,因為這些操作,都涉及額外的對象創建和銷毀的工作。
- 不管是「裝箱」還是「拆箱」,都會有比較明顯的性能問題
- 因為這些操作,都涉及額外的對象創建和銷毀
範例
1
2
3
4
5
6
7
8
9
10
11
12
var array = new ArrayList();
array.Add(1); //裝箱
array.Add("ddd"); //不裝箱
array.Add(DateTime.Today); //裝箱
//嚴重問題
//1.類型不安全
var number = (int)array[1]; //運行時會報錯
//2.可能會有裝箱操作,性能有問題
//儘量使用泛型來代替
var list = new List<int>();
list.Add(1);