承接上篇,本節深入探討 C# 高階語言特性在 IL 層面的映射機制:介面方法重寫、建構函數/解構函數非繼承性、循環引用檢測差異、存取修飾符編譯時約束、型別轉換與 castclass、sealed/abstract 語義、const/readonly 編譯期與運行期行為、靜態/實例方法調用約定、基類建構函數鏈與 base/this 編譯期展開、私有建構函數與物件創建限制、欄位預設值初始化、三元/邏輯/位運算符 IL 降級實現。透過對比 C# 原始碼與 IL 輸出,揭示「語言糖衣」背後的運行時真相。
C# 允許類以不同名稱實現介面方法,IL 使用 .override 指令建立映射關係。
// C#: 類 yyy 實現介面 iii 的 pqr(),但方法命名為 abc()
.override iii::pqr with instance void yyy::abc()
關鍵點:override 指令強制編譯器驗證類確實實現了介面,否則拋 TypeLoadException。這是編譯期約束在運行期的強制回聲。
// C#: ~zzz() { Console.WriteLine("hi"); }
// IL:
.method family hidebysig virtual instance void Finalize() cil managed
{
ldstr "hi"
call void [mscorlib]System.Console::WriteLine(string)
ldarg.0
call instance void [mscorlib]System.Object::Finalize()
ret
}
Finalize()(family = protected,virtual)。base.Finalize()(IL 強制,C# 自動生成)。base(...))。Finalize。.ctor() 呼叫 base.ctor(),無其他建構函數複製。
// IL: .class yyy extends [mscorlib]System.Array { ... }
// C# 編譯器禁止,IL 運行時允許。
揭示:語言規則 = 編譯器約束,非運行時約束。C# 禁止繼承 Delegate、Enum、ValueType、Array,IL 無此限制。
// IL 允許:aa → bb → cc → aa 循環繼承
// 運行時拋出:TypeLoadException: Could not load class 'aa' because the format is bad
C# 編譯器靜態檢測循環引用並報錯;IL 無靜態檢測,導致運行時以難以理解的異常呈現。
internal class 編譯為 .class private(組件內可見)。internal 概念,僅有 public / private(組件外不可見)。private + 同組件引用權限實現。.class public yyy extends private xxx 合法;.method public static void vijay() 在 private class zzz 中合法。
// C#: b = (xxx)a;
// IL:
ldloc.0 // a
castclass xxx // 嘗試轉換
stloc.1 // b
castclass 運行期檢查型別相容性,失敗拋 InvalidCastException。castclass 裁決。sealed → .class sealed:運行期禁止繼承(Beta 版可能未強制,正式版已強制)。abstract → .class abstract:禁止直接 newobj 實例化,僅允許繼承。
// C#: const int i = 10;
// IL: 方法體中直接 ldc.i4.s 10,無欄位定義
// C#: const int i = j + 4; const int j = k - 1; const int k = 3;
// IL: .field private static literal int32 i = int32(6) ... 欄位僅存最終計算值
literal 標記 = 編譯期常數,不佔用運行期記憶體。stsfld 寫入 literal 欄位 → 運行期 MissingFieldException。
// C#: static readonly int i = 10;
// IL: .field public static initonly int32 i
// 靜態建構函數 .cctor 中初始化:ldc.i4.s 10; stsfld i
initonly = 只能在建構函數(.ctor/.cctor)中賦值。stsfld 寫入。this(ldarg.0 為第一個參數)。this 為第一個參數(ldarg.0 = this),呼叫前必須 ldloc.0 載入物件引用。IL 文檔規定順序為聲明順序(左到右入棧),但 Microsoft 實際實現為右到左(C 語言 cdecl 慣例)。反組譯器顯示順序可能與實際調用不符,不可推斷。
// C#: bb() : this(20) { ... } bb(int i) : base(i) { ... }
// IL: 無參建構函數 → 直接 call instance void bb::.ctor(int32) → 再 call base.ctor
base、this 在 IL 不存在,純屬編譯器語法糖,硬編碼為對應建構函數呼叫序列。.ctor → 外部 newobj 失敗,拋 MethodAccessException。int、bool 等)在物件分配時自動歸零(0/false)。.cctor 前由運行時歸零;實例欄位在 .ctor 前歸零。
// C#: j = i >= 20 ? "hi" : "bye";
// IL: bge.s (>=跳轉) + br.s (無條件跳轉) 實現 if-else 邏輯
無法從 IL 反推原始是 if 還是 ?:。
// C#: if (i >= 4 & j > 1)
// IL: clt (小於) + ceq (等於0) → 結果0/1 → cgt (大於) → and (位與)
非短路,雙邊必評估。
// IL: blt.s (若假跳出) → ble.s (若假跳出) → 執行體
blt.s = clt + brtrue;ble.s = cgt + brfalse。首條假則跳過後續,效能優於 &。
異或:僅一邊真則真,雙真則假。IL 直接支援 xor。
!=:ceq 比較 → 結果 0/1 → ceq 與 0 比較(取反)。!:ceq 與 0 比較(真→假,假→真)。本章揭示了 C# 豐富語法特性在 IL 層面的「真面目」:
call/callvirt、castclass/isinst、newobj、ldarg/ldloc/stloc、br/brtrue/brfalse、add/sub/mul/div、and/or/xor、ceq/cgt/clt。掌握 IL 視角,能讓你從「會用語言」進化為「懂運行時」,在效能調優、反編譯分析、跨語言互操作、框架設計中佔據制高點。