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

Emit 實戰:For 與 Foreach 循環的 IL 實現差異深度解析

     編輯:關於.NET

傳統認知認為 foreach 編譯出的 IL 比 for 更優化。通過手寫 Emit 實現兩種循環求和,並對比反編譯 IL,發現 foreach 實際生成更多指令——原來優化點在於語義安全而非指令數量。本文完整還原兩種循環的 IL 邏輯並解析差異根因。

目標 C# 代碼

public int ForMethod(int[] ints)
{
    int sum = 0;
    for (int i = 0; i < ints.Length; i++)
        sum += ints[i];
    return sum;
}

public int ForeachMethod(int[] ints)
{
    int sum = 0;
    foreach (int i in ints)
        sum += i;
    return sum;
}

For 循環 IL 關鍵邏輯

// 局部變量: sum(0), i(1)
L_0000: ldc.i4.0; stloc.0      // sum = 0
L_0002: ldc.i4.0; stloc.1      // i = 0
L_0004: br.s L_001E            // 跳轉到條件檢查

L_0006: ldloc.0                // sum
L_0007: ldarg.0                // ints
L_0008: ldloc.1                // i
L_0009: ldelem.i4              // ints[i]
L_000A: add; stloc.0           // sum += ints[i]

L_000C: ldloc.1; ldc.i4.1; add; stloc.1  // i++

L_001E: ldloc.1                // i
L_001F: ldarg.0; ldlen; conv.i4 // ints.Length
L_0022: clt; brtrue.s L_0006   // i < len ? 進入循環體

L_0024: ldloc.0; ret           // return sum

Foreach 循環 IL 關鍵邏輯

// 局部變量: sum(0), i(1), ints(2), index(3)
L_0000: ldc.i4.0; stloc.0      // sum = 0
L_0002: ldarg.0; stloc.2       // ints = 參數數組 (關鍵差異 1)
L_0004: ldc.i4.0; stloc.3      // index = 0
L_0006: br.s L_0020

L_0008: ldloc.2                // ints
L_0009: ldloc.3                // index
L_000A: ldelem.i4              // ints[index]
L_000B: stloc.1                // i = ints[index] (關鍵差異 2)

L_000C: ldloc.0; ldloc.1; add; stloc.0 // sum += i

L_0010: ldloc.3; ldc.i4.1; add; stloc.3 // index++

L_0020: ldloc.3                // index
L_0021: ldloc.2; ldlen; conv.i4 // ints.Length
L_0024: clt; brtrue.s L_0008

L_0026: ldloc.0; ret

核心差異對比

維度ForForeach
數組訪問直接 ldarg.0 使用參數先 stloc 存局部變量,再 ldloc 讀取
元素獲取ldelem 直接加到 sum先 ldelem 存 i,再 ldloc i 加到 sum
指令數量較少 (~18 條)較多 (~22 條)
邊界檢查隱含在 ldelem同樣隱含
語義保證無數組引用固定,防止循環中數組被修改

為什麼 Foreach 看似「更優」?

  1. 語義安全:C# 規格規定 foreach 遍歷的是迭代器快照,數組引用不可變。Emit 中體現為先將數組存入局部變量,避免循環體內修改參數引用導致邏輯錯誤。
  2. 統一模式:foreach 編譯為通用模式 (GetEnumerator/MoveNext/Current),對數組有特化優化,但仍保留統一語義結構。
  3. JIT 進一步優化:Runtime JIT 會將兩者優化為相同機器碼,IL 層面指令差異不代表運行效能差異。

Emit 實現建議

  • 手寫高性能循環時,優先參考 for 模式,指令更少、可讀性更強。
  • 若需語義兼容 IEnumerable,再實現枚舉器模式 (Callvirt MoveNext + Callvirt get_Current)。
  • 使用 il.DefineLabel() 管理跳轉,避免硬編碼偏移量。

結論:IL 指令數量不是性能唯一指標,foreach 的「多指令」換取的是語義正確性與泛化能力。

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