首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> C# >> 關於C#

《C# to IL》第四章 關鍵字與操作符(上):返回值優化、建構函數、命名空間、ref/out、字串操作、預處理指令、繼承與虛方法調用機制

     編輯:關於C#

前言

本章開啟「C# to IL」核心篇章,通過大量對比範例,系統展示 C# 基礎語法結構如何編譯為 IL 指令序列。涵蓋:死碼消除、預設建構函數機制、命名空間合併、ref/out 統一為位址傳遞、字串連接與比較編譯期優化、字面量編碼、關鍵字逸出、註解剝離、字串轉義與逐字字串、預處理指令編譯期執行、繼承與虛方法調度、new/override/base 語義差異、介面實現與 .override。透過 IL 視角重構 C# 認知體系。

一、 死碼消除:return 後代碼不生成 IL


// C#: return; Console.WriteLine("hi");
// IL: br.s IL_0002; IL_0002: ret
// 編譯器發出警告,IL 中無 WriteLine 調用

二、 建構函數:預設無參 vs 顯式定義

  • 無建構函數 → 編譯器生成無參 .ctor() 呼叫 base.ctor()。
  • 有建構函數 → 預設無參不再生成(C# 與 IL 一致)。
  • 基類無參建構函數總是最先被調用(IL 強制,C# 隱式插入)。

三、 命名空間:巢狀合併為單一完整名稱


// C#: namespace vijay { namespace mukhi { class zzz } } }
// IL: .namespace vijay.mukhi { .class zzz ... }
// 多個同名命名空間在同一組件中會被編譯器合併

IL 僅支援單層完整命名空間,巢狀結構為語言糖衣。

四、 ref/out 參數:IL 中統一為 managed pointer (&)


// C#: void abc(ref int i) { i = 10; }
// IL: .method ... abc(int32& i)  // 參數型別標記 &
// 呼叫端: ldloca.s V_0 (取址)
// 方法內: ldarg.1 (載入位址) → ldc.i4.s 10 → stind.i4 (間接存儲)
  • ldloca.s 載入區域變數位址;ldarg.1 載入參數位址。
  • stind.i4 將值透過位址間接存儲。
  • ref/out 在 IL 無差異,均為位址傳遞;語義區分純屬 C# 編譯期檢查(out 必須在方法內賦值)。
  • 反組譯無法區分 ref/out,資訊編譯期丟失。

五、 字串操作:編譯期優化與 String.Concat

1. 常量連接:編譯期合併


// C#: "hi" + "bye" → IL: ldstr "hibye"

2. 變數連接:String.Concat 靜態方法


// C#: s + "bye" → IL: call string String::Concat(string, string)

3. 字串比較:String.Equals 靜態方法


// C#: a == b → IL: call bool String::Equals(string, string)

證明:字串運算為 C# 編譯器內建語義,IL 僅見方法調用。

六、 型別轉換:零成本轉換


// C#: (char)65 → IL: ldc.i4.s 65 → call WriteLine(wchar)
// C#: (char)i → IL: 變數型別為 wchar,調用時直接作為 wchar 傳遞
  • 數值型別轉換僅改變調用約定中的參數型別標記,無運行期指令開銷。
  • 字元字面量 'A'、Unicode '\u0041'、十六進位 0x41 在 IL 均為 ldc.i4.s 65。

七、 關鍵字逸出:@ 允許保留字作識別碼


// C#: int @int; → IL: .locals (int32 V_0) 區域變數;.field private int32 'int' 實例欄位
// @ 符號編譯期移除,IL 中以單引號包裹保留字欄位名

八、 註解:完全剝離,IL 無痕跡

單行 //、多行 /* */、XML 文檔註解 /// 均不生成任何 IL 指令或元數據(除非編譯器選項輸出 XML 文檔檔案)。

九、 字串字面量:轉義與逐字字串

C# 寫法IL 實際字串說明
"hi\nBye\tNo"hi<LF>Bye<TAB>No轉義序列編譯期展開
@\"hi\nBye\tNo\"hi\nBye\tNo逐字字串:忽略轉義,換行實際換行
\"\"\"\""雙引號轉義

十、 預處理指令:純編譯期邏輯,IL 無痕

  • #define/#undef:條件編譯開關,決定哪些程式碼進入 IL。
  • #if/#else/#endif:編譯期分支,僅保留真分支 IL。
  • #warning:編譯器輸出警告,不影響 IL。
  • #line/#error:除錯/錯誤指令,無可執行輸出。
  • 反組譯無法還原預處理邏輯,資訊單向流失。

十一、 繼承:extends 關鍵字源自 IL/Java


// C#: class xxx : yyy
// IL: .class xxx extends yyy
  • 非虛方法調用:編譯器靜態綁定(call yyy::abc())。
  • 虛方法調用:callvirt 運行期根據物件實際型別分派。

十二、 虛方法調度:new vs override vs base

C# 修飾符IL 方法標記調用指令分派邏輯
(無)無call靜態綁定至宣告類型
virtualvirtual + newslotcallvirt運行期分派,新虛擬槽位
overridevirtual (無 newslot)callvirt覆寫基類虛擬槽位
new無 virtualcall隱藏基類同名方法,靜態綁定

關鍵範例:base.abc() 編譯為非虛調用


// C#: base.abc();
// IL: ldarg.0; call instance void yyy::abc()  // 直接呼叫基類實現,忽略虛擬分派

base 為編譯期關鍵字,IL 中無對應指令。

newslot 意義

  • 標記「此虛方法不覆寫基類同名虛方法,另開新槽位」。
  • 配合 override 實現多態覆寫:基類 newslot virtual、派生類 virtual(無 newslot) → 運行期分派至派生類。

十三、 介面實現:.override 指令映射不同名方法


// C#: class yyy : iii { void abc() { } }  // 介面 iii 有 pqr()
// IL: .class yyy implements iii
//     .override iii::pqr with instance void yyy::abc()

允許介面方法與實現方法名稱不同,.override 建立映射。呼叫介面方法時實際調用映射的實現方法。

十四、 總結

上篇確立了 C#→IL 映射的核心規律:

  1. 編譯期優化:死碼消除、常量折疊、字串連接/比較內建、預處理指令展開。
  2. 語法糖衣降級:ref/out→位址、@逸出→引號、base/this→硬編碼呼叫鏈、轉換→零成本約定變更。
  3. 物件模型映射:繼承→extends、虛方法→virtual/callvirt/newslot、介面→implements/.override、建構函數→.ctor/.cctor、解構函數→Finalize。
  4. 語言與運行時分層:存取修飾符、循環引用檢測、readonly 強制、sealed/abstract 執行,屬編譯器職責;CLR 提供最小驗證與指令集。

下篇將深入 const/readonly 細節、建構函數鏈、運算符降級、私有建構函數等進階主題。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved