系列最終回,以完整範例程式碼,從 IL、記憶體、執行行為三維度,全方位剖析值類型 (struct) 與引用類型 (class) 的核心差異。
public struct MyStruct {
private int _myNo;
public int MyNo { get => _myNo; set => _myNo = value; }
public MyStruct(int n) => _myNo = n;
public void ShowNo() => Console.WriteLine(_myNo);
}
public class MyClass {
private int _myNo;
public int MyNo { get => _myNo; set => _myNo = value; }
public MyClass() => _myNo = 0;
public MyClass(int n) => _myNo = n;
public void ShowNo() => Console.WriteLine(_myNo);
}
IL 差異:struct 為 sequential ansi sealed,繼承 ValueType;class 為 auto ansi beforefieldinit,繼承 Object。
MyStruct s1 = new MyStruct(); // 堆疊,預設 0
MyClass c1; // 堆疊唯變數,null
c1 = new MyClass(); // 堆配置物件,c1 指向它
s1.MyNo = 1; // 直接修改堆疊值
c1.MyNo = 2; // 經指標修改堆上物件
MyStruct s2 = s1; // 成員逐一複製 (深拷貝)
MyClass c2 = c1; // 複製引用 (淺拷貝)
s1.MyNo = 3; // s2 不變
c1.MyNo = 4; // c2 同步改變
ref/out 實戰static void ValRef(ref int i) { i = 100; }
static void ValOut(out int i) { i = 200; }
static void RefRef(ref object o) { o = new MyStruct(); } // 裝箱
static void RefOut(out object o) { o = new string('a',10); }
ref 需預先初始化;out 不需,但方法內必賦值ref 可直接修改原變數ref 可替換原變數指向// struct 顯式轉換 int → MyStruct
public static explicit operator MyStruct(int n) => new MyStruct(n);
// class 隱式轉換 MyClass → string
public static implicit operator string(MyClass c) => c.ToString();
| 方法 | 值類型 | 引用類型 |
|---|---|---|
ReferenceEquals | 永遠 false (兩次裝箱) | 同一物件 true |
Equals (虛) | ValueType 重寫:比較欄位值 | 預設比較引用,常重寫比值 |
== | 未重載則位元比較 | 預設比較引用,常重載比值 |
MyClass c1 = new MyClass(); // 堆 A
MyClass c2 = c1; // 同指 A
MyClass c3 = new MyClass(); // 堆 B
c1 = c3; c2 = c3; // A 成為垃圾,等待 GC
| 場景 | 推薦 | 理由 |
|---|---|---|
| 資料小、邏輯簡單、無多型、高頻建立 | struct | 堆疊配置、無 GC、複製語義安全 |
| 需繼承、多型、複雜行為、參考語義 | class | 支援 OOP 完整特性、堆配置共享 |
| 頻繁裝箱/集合存儲 | 避免 struct | 裝箱產生額外堆分配、效能損耗 |
掌握記憶體模型,才能在效能與設計間做出正確抉擇。