語法結構
foreach (Type variable in enumerable)
statement;
// 等價展開 (簡化版)
IEnumerator enumerator = enumerable.GetEnumerator();
try {
while (enumerator.MoveNext()) {
Type variable = (Type)enumerator.Current;
statement;
}
}
finally {
(enumerator as IDisposable)?.Dispose();
}
集合型別契約:鴨子類型
C# foreach 不要求實作 IEnumerable,只需符合模式:
| 需求 | 說明 |
公開 GetEnumerator() | 非靜態,回傳型別為類別/結構/介面 |
列舉器有 MoveNext() | 公開、非靜態、回傳 bool |
列舉器有 Current 屬性 | 公開、非靜態、可讀 |
這叫「鴨子類型」:長得像列舉器就能用,不必繼承介面。但實作 IEnumerable/IEnumerable<T> 可獲得 LINQ、標準庫互通性。
迴圈變數唯讀語意
foreach (var item in list)
{
item = newObj; // CS1656: 無法指派給 foreach 迴圈變數
item.Property = 123; // 允許:修改參考型別物件內容
// 實值型別則完全不可修改 (複製品)
}
item 是唯讀區域變數
- 不可作為
ref/out 參數傳遞
- 參考型別:變數唯讀,但物件內容可修改
型別轉換規則
IEnumerable
集合元素型別需隱含轉換至迴圈變數型別,否則編譯錯誤。
陣列的特殊支援
- 一維陣列:索引 0 到 Length-1 依序
- 多維陣列:最右維度最先遞增 (Row-major)
int[,] m = { {1,2}, {3,4}, {5,6} };
foreach (var v in m) Console.Write(v + " "); // 1 2 3 4 5 6
- 陣列
foreach 編譯器優化為索引存取,不建立列舉器物件,效能最佳
break / continue 行為
foreach (var item in collection)
{
if (skipCondition) continue; // 跳過本項,進入下一項
if (stopCondition) break; // 完全離開迴圈
Process(item);
}
行為與 while 迴圈一致。
效能比較
| 型別 | foreach 效能 | 備註 |
| 陣列 (T[]) | ★★★★★ | 索引存取,無分配 |
| List<T> | ★★★★☆ | 結構體列舉器,無 boxing |
| 自訂 IEnumerable<T> | ★★★☆☆ | 介面調用、列舉器物件分配 |
| 非泛型 IEnumerable | ★★☆☆☆ | boxing、型別轉換開銷 |
常見陷阱
- 修改集合內容:
foreach 中 list.Add/Remove → InvalidOperationException (列舉器偵測版本變更)
- 實值型別列舉器:
List<T>.Enumerator 為 struct,foreach 複製列舉器,若手動 GetEnumerator() 忘記 using 可能導致邏輯錯誤
- 捕捉迴圈變數:C# 5.0+
foreach 變數每次迴圈重新宣告,Lambda 捕捉正確;for 迴圈變數共享,需手動複製
總結
foreach 是 C# 遍歷集合的標準語法,語意清晰、支援鴨子類型、迴圈變數唯讀、陣列有編譯器優化。理解列舉器模式、唯讀語意、效能特性,能寫出正確且高效的迴圈程式碼。
- 上一篇文章: C# 前置處理器指令:#define、#undef 符號定義與作用域規則
- 下一篇文章: C# for 迴圈完整指南:語法結構、巢狀迴圈、跳轉控制與實戰範例
Copyright ©
程式碼之家 All Rights Reserved