C# 擁有統一類型系統:所有類型 (含基本型別) 皆繼承自 object。這設計讓基本型別 (int、bool 等) 也能擁有方法 (如 ToString()),並能無縫存入集合、當作參數傳遞。本文深入剖析值類型與參考類型的記憶體行為、裝箱/拆箱機制及效能影響。
值類型變數直接包含資料,指派時發生位元複本。分類如下:
| C# 關鍵字 | .NET 型別 | 大小 | 範圍 / 精度 | 預設值 |
|---|---|---|---|---|
sbyte | System.SByte | 8 位元 | -128 ~ 127 | 0 |
byte | System.Byte | 8 位元 | 0 ~ 255 | 0 |
short | System.Int16 | 16 位元 | -32,768 ~ 32,767 | 0 |
ushort | System.UInt16 | 16 位元 | 0 ~ 65,535 | 0 |
int | System.Int32 | 32 位元 | -2,147,483,648 ~ 2,147,483,647 | 0 |
uint | System.UInt32 | 32 位元 | 0 ~ 4,294,967,295 | 0 |
long | System.Int64 | 64 位元 | -9.22×10¹⁸ ~ 9.22×10¹⁸ | 0L |
ulong | System.UInt64 | 64 位元 | 0 ~ 1.84×10¹⁹ | 0UL |
float | System.Single | 32 位元 | ±1.5×10⁻⁴⁵ ~ ±3.4×10³⁸ (7 位精度) | 0.0f |
double | System.Double | 64 位元 | ±5.0×10⁻³²⁴ ~ ±1.7×10³⁰⁸ (15-16 位精度) | 0.0 |
decimal | System.Decimal | 128 位元 | ±1.0×10⁻²⁸ ~ ±7.9×10²⁸ (28-29 位精度) | 0.0m |
bool | System.Boolean | 1 位元 (實際 1 byte) | true / false | false |
char | System.Char | 16 位元 | U+0000 ~ U+FFFF (單一 Unicode 字元) | '\0' |
int 固定 32 位元,不隨平台變化。long 固定 64 位元。bool 嚴格只能是 true/false,不可 隱含轉換自整數 (杜絕 if (x = 5) 這類 Bug)。char 為 UTF-16 代碼單元,非 ASCII。public struct IPAddress
{
public byte Octet1, Octet2, Octet3, Octet4;
public IPAddress(byte o1, byte o2, byte o3, byte o4)
{
Octet1 = o1; Octet2 = o2; Octet3 = o3; Octet4 = o4;
}
public override string ToString() => $"{Octet1}.{Octet2}.{Octet3}.{Octet4}";
}
適用場景:小型、不可變、邏輯單一的資料結構 (如座標點、顏色、日期時間區間)。避免參考類型的堆積配置與 GC 壓力。
public enum Month : byte // 指定底層型別為 byte 節省空間
{
January = 1, February = 2, March = 3, April = 4,
May = 5, June = 6, July = 7, August = 8,
September = 9, October = 10, November = 11, December = 12
}
預設底層型別為 int,可指定 byte, sbyte, short, ushort, int, uint, long, ulong。
參考類型變數存放「物件參考」(記憶體位址),指派時複製參考,指向同一物件。分類:
class — 單一繼承、支援介面、完整 OO 功能。interface — 定義契約,可多重實作。delegate — 類型安全的函式指標,事件基礎。string — 不可變參考類型,== 比較值而非參考。T[] — 階層固定、元素同型別。System.Object — 所有類型的共同基類。int value = 2024;
object boxed = value; // 裝箱發生
// IL 指令:box System.Int32
// 記憶體動作:
// 1. 在堆積配置新物件 (含型別標頭、同步區塊索引、資料副本)
// 2. 將值型別資料複製進物件
// 3. 回傳物件參考給 boxed 變數
object boxed = 2024;
int unboxed = (int)boxed; // 拆箱發生
// IL 指令:unbox.any System.Int32
// 記憶體動作:
// 1. 檢查 boxed 是否為 null
// 2. 驗證物件實際型別是否為 int (否則拋 InvalidCastException)
// 3. 取得物件內資料位址,複製資料到堆疊變數 unboxed
int original = 100;
object boxed = original;
boxed = 200; // 修改 boxed 參考指向的新物件 (或重新裝箱)
Console.WriteLine($"original={original}, boxed={boxed}"); // 輸出: 100, 200
裝箱建立的是資料副本,兩者生命週期與內容完全獨立。
| 操作 | 成本 | 建議 |
|---|---|---|
| 裝箱 | 堆積配置 + 複製 + GC 壓力 | 避免在熱路徑 (迴圈、高頻呼叫) 裝箱 |
| 拆箱 | 型別檢查 + 複製 | 同左 |
| 非泛型集合 | 每次存取皆裝箱/拆箱 | 務必使用泛型集合 (List<T>, Dictionary<TKey,TValue>) |
| 介面呼叫值類型 | 裝箱 | 若頻繁呼叫,考慮讓結構實作介面並透過泛型約束 where T : IInterface 避免裝箱 |
// 1. String.Format / Console.WriteLine 參數陣列
Console.WriteLine("Value: {0}", 42); // 42 被裝箱
// 2. 非泛型集合
ArrayList list = new ArrayList();
list.Add(42); // 裝箱
int x = (int)list[0]; // 拆箱
// 3. 介面呼叫
IComparable cmp = 42; // 裝箱
cmp.CompareTo(10); // 虛擬呼叫
// 改善:使用泛型
List list = new List();
list.Add(42); // 無裝箱
int x = list[0]; // 直接存取
// 唯讀結構:防止防禦性複製,提升效能
public readonly struct Vector3
{
public double X, Y, Z;
public Vector3(double x, double y, double z) { X=x; Y=y; Z=z; }
public double Length => Math.Sqrt(X*X + Y*Y + Z*Z); // 唯讀屬性
}
// ref struct:只能存在於堆疊,不可裝箱、不可為類別欄位、不可用於 async/iterator
public ref struct SpanWrapper
{
private Span _span;
public SpanWrapper(Span span) => _span = span;
}
readonly struct、ref struct 進一步釋放值類型潛能。深入理解類型系統,是寫出高效能、低 GC 壓力、無驚喜 Bug 的 C# 程式碼的前提。