foreach 不只是語法糖,編譯器會根據集合實際型別產生最優迭代 IL。對陣列避免裝箱、對多維陣列自動處理邊界、對介面型別自動轉型、甚至自動處理 IDisposable 列舉器。取代 for、while、do-while,寫更少、更快、更穩健的程式碼。
int[] foo = new int[100];
// 1. foreach - 最佳(C# 1.1+)
foreach (int i in foo) Console.WriteLine(i);
// 2. for 使用 Length - 次佳
for (int i = 0; i < foo.Length; i++) Console.WriteLine(foo[i]);
// 3. 提取 Length 到區域變數 - 最差!
int len = foo.Length;
for (int i = 0; i < len; i++) Console.WriteLine(foo[i]);
第 3 種阻礙 JIT 邊界檢查提升:執行期仍對每次迭代檢查 foo.Length,反而產生雙重邊界檢查。foreach 與第 2 種讓 JIT 證明索引安全,移除邊界檢查。
早期 C# 1.0 foreach 透過 IEnumerator 導致裝箱。現代編譯器對陣列產生索引式迴圈 IL,零裝箱:
// 產生 IL 等同於:
for (int i = 0; i < foo.Length; i++) Console.WriteLine(foo[i]);
// 二維棋盤
private Square[,] _board = new Square[8, 8];
// 手寫巢狀 for 需知維度與下界
foreach (Square sq in _board) sq.PaintSquare(); // 自動處理所有維度
// 非零下界陣列(罕見但合法)
var arr = Array.CreateInstance(typeof(int), new[]{5}, new[]{1}); // 索引 1~5
foreach (int v in arr) Console.WriteLine(v); // 正確迭代
int[] foo = new int[100];
// 後來改為 ArrayList
ArrayList foo = new ArrayList(100);
// for 迴圈全壞:Length→Count、foo[i] 回傳 object 需轉型
// foreach 完全不動:編譯器自動產生正確 IL
實作任一介面即可:
IEnumerable(GetEnumerator())IEnumeratorGetEnumerator() 回傳 IEnumerator// 編譯器產生等效碼:
IEnumerator it = foo.GetEnumerator();
using (IDisposable d = it as IDisposable)
{
while (it.MoveNext())
{
int elem = (int)it.Current;
// ...
}
}
列舉器實作 IDisposable 時自動包裝 using,確保資源釋放。
foreach:最少程式碼、最佳效能、正確處理邊界/轉型/資源、重構最彈性。迭代集合的首選,無例外。