數據的類型定義了存儲數據需要的內存大小及組成該類型的數據成員。類型還決定了對象在內存中的存儲位置——棧或堆。
類型被分為兩種:值類型和引用類型,這兩種類型的對象在內存中的存儲方式不同。
- 值類型只需要一段單獨的內存,用於存儲實際的數據。
- 引用類型需要兩段內存。
- 第一段存儲實際的數據,它總是位於堆中。
- 第二段是一個引用,指向數據在堆中的存放位置。
下圖展示了每種類型的單個數據項是如何存儲的。對於值類型,數據存放在棧里。對於引用類型,實際數據存放在堆里而引用存放在棧里。

值類型
所有值類型都隱式派生自 System.ValueType
,下表顯示 C# 值類型:
值類型 | 類別 | 類型后綴 |
---|---|---|
bool |
Boolean |
|
byte |
無符號、數字、整型 | |
char |
無符號、數字、整型 | |
decimal |
數字、浮點 | M 或 m |
double |
數字、浮點 | D 或 d |
enum |
枚舉 | |
float |
數字、浮點 | F 或 f |
int |
帶符號、數字、整型 | |
long |
帶符號、數字、整型 | L 或 l |
sbyte |
帶符號、數字、整型 | |
short |
帶符號、數字、整型 | |
struct |
用戶定義的結構 | |
uint |
無符號、數字、整型 | U 或 u |
ulong |
無符號、數字、整型 | UL 、Ul 、uL 、ul 、LU 、Lu 、lU 或 lu |
ushort |
無符號、數字、整型 |
值類型直接包含值,換言之,變量引用的位置就是內存中實際存儲值的位置。
因此,將一個值賦給變量 1,再將變量 1 賦給變量 2,會在變量 2 的位置創建值的拷貝,而不是引用變量 1 的位置。
這進一步造成更改變量 1 的值不會影響變量 2 的值。
下圖對此進行了演示。number1
引用內存中的特定位置,該位置包含值 42
。將 number1
的值賦給 number2
之后,兩個變量都包含值 42
。但修改其中任何一個值都不會影響另一個值。

類似地,將值類型的實例傳給 Console. WriteLine()
這樣的方法也會生成內存拷貝。在方法內部對參數值進行的任何修改都不會影響調用函數中的原始值。
引用類型
引用類型的變量存儲對數據存儲位置的引用,而不是直接存儲數據。要去那個位置才能找到真正的數據。所以為了訪問數據,“運行時”[1] 要先從變量中讀取內存位置,再“跳轉”到包含數據的內存位置。
為引用類型的變量分配實際數據的內存區域稱為堆(heap)。

由於引用類型只拷貝對數據的引用,所以兩個不同的變量可引用相同的數據。因此,對一個變量執行的操作會影響另一個變量所引用的對象。無論賦值還是方法調用都會如此。因此,如果在方法內部更改引用類型的數據,方法執行完成之后,將看到更改后的結果。
總結
一個類型要么是值類型,要么是引用類型。區別在於數據存儲的方式:對於值類型,數據存放在棧里。對於引用類型,實際數據存放在堆里而引用存放在棧里。
引用類型的變量存儲對其數據(對象)的引用,而值類型的變量直接包含其數據。 對於引用類型,兩個變量可引用同一對象;因此,對一個變量執行的操作會影響另一個變量所引用的對象。 對於值類型,每個變量都具有其自己的數據副本,對一個變量執行的操作不會影響另一個變量。