「靜態方法能否呼叫實例欄位?」「實例方法能否直接呼叫靜態方法?」這類問題在 Code Review 中頻繁出現。本文用記憶體模型、IL 指令、編譯器規則三個視角,徹底釐清靜態/非靜態成員的存取邊界與最佳實務。
| 特性 | 靜態成員 | 實例成員 |
|---|---|---|
| 所屬對象 | 型別本身 (Type Object) | 具體實例 (Object Instance) |
| 記憶體位置 | High Frequency Heap (靜態欄位) / Code Heap (方法) | Managed Heap (實例欄位) / Code Heap (方法共用) |
| 生命週期 | AppDomain 存活期間 (或 Module 卸載) | 實例被 GC 回收前 |
| 共享性 | 全域單一份 | 每個實例獨立一份 |
| 呼叫語法 | `TypeName.StaticMember` | `instance.InstanceMember` |
public class Demo
{
// 欄位
public static int StaticField = 1;
public int InstanceField = 2;
// 靜態建構子
static Demo()
{
StaticField = 10; // ✅ 合法
// InstanceField = 20; // ❌ CS0120: 需要物件參考
}
// 靜態方法
public static void StaticMethod()
{
Console.WriteLine(StaticField); // ✅
// Console.WriteLine(InstanceField); // ❌ CS0120
// InstanceMethod(); // ❌ CS0120
// ✅ 透過實例參數存取
var d = new Demo();
Console.WriteLine(d.InstanceField);
d.InstanceMethod();
}
// 實例方法
public void InstanceMethod()
{
Console.WriteLine(InstanceField); // ✅ 隱含 this.InstanceField
Console.WriteLine(StaticField); // ✅ 直接存取靜態 (建議 Demo.StaticField)
StaticMethod(); // ✅ 直接呼叫靜態方法
this.InstanceMethod(); // ✅ 遞迴
}
// 實例建構子
public Demo()
{
InstanceField = 100; // ✅
StaticField = 200; // ✅ (但會影響所有實例)
StaticMethod(); // ✅
}
// 靜態屬性
public static int StaticProp { get; set; } = 3;
// 實例屬性
public int InstanceProp
{
get => InstanceField;
set => InstanceField = value;
}
}
// 靜態方法呼叫靜態欄位
IL_0000: ldsfld int32 Demo::StaticField
IL_0005: call void [System.Console]System.Console::WriteLine(int32)
// 實例方法呼叫實例欄位
IL_0000: ldarg.0 // this
IL_0001: ldfld int32 Demo::InstanceField
IL_0006: call void [System.Console]System.Console::WriteLine(int32)
// 實例方法呼叫靜態欄位
IL_0000: ldsfld int32 Demo::StaticField
IL_0005: call void [System.Console]System.Console::WriteLine(int32)
// 靜態方法嘗試呼叫實例欄位 → 編譯期直接報錯,不會產生 IL
`ldsfld` (load static field) vs `ldfld` (load instance field, 需要 `this` 在堆疊上)。
| 陷阱 | 錯誤寫法 | 正確寫法 |
|---|---|---|
| 靜態方法用 this | `static void F() { this.Method(); }` | 傳入實例參數 `static void F(Demo d) { d.Method(); }` |
| 靜態屬性回傳實例狀態 | `static int Count => _list.Count;` (_list 為實例欄位) | 改為實例屬性,或 `_list` 改靜態 |
| 事件處理器註冊靜態方法 | `btn.Click += StaticHandler;` (需存取 UI 控制項) | 改實例方法,或靜態方法傳入所需控制項參考 |
| 單例模式雙重檢查 | 靜態欄位 `_instance` + 實例建構子邏輯混淆 | 用 `Lazy |
public class BeforeFieldInit // 預設:標記 beforefieldinit
{
public static int X = ComputeX(); // 可能在第一次存取任何靜態/實例成員前執行 (JIT 決定)
}
public class Precise // 精確語意:有靜態建構子
{
static Precise() { X = ComputeX(); } // 保證在第一次存取本類型任何成員前執行
public static int X;
}
若需精確控制初始化時機 (如登入 License、載入 Native DLL),請**顯式宣告靜態建構子**。
核心口訣:靜態不識實例,實例隨意用靜態。編譯器在靜態語境封鎖 `this`,強迫你顯式傳入實例參考。理解這條紅線,即能避開 90% 的靜態/非靜態呼叫錯誤,並寫出語意清晰、易測試的程式碼。