迴圈 讓程式重複執行程式碼區塊。C# 提供四種主要迴圈結構,各有適用場景。掌握迴圈語法、跳脫控制、效能特性,能寫出清晰、高效的演算法實作。
| 迴圈類型 | 判斷時機 | 適用場景 | 關鍵字 |
|---|---|---|---|
for | 每次迭代前 | 已知迭代次數、索引存取、計數器控制 | for(init; cond; step) |
foreach | 每次迭代前 | 遍歷集合/陣列、唯讀存取、簡潔語法 | foreach(var x in coll) |
while | 每次迭代前 | 未知次數、條件複雜、前置檢查 | while(cond) |
do-while | 每次迭代後 | 至少執行一次、選單系統、輸入驗證 | do { } while(cond); |
for 迴圈:完全控制// 標準形式
for (int i = 0; i < 10; i++) {
Console.WriteLine(i);
}
// 多變數初始化/遞增
for (int i = 0, j = 10; i < j; i++, j--) {
Console.WriteLine($"{i}, {j}");
}
// 省略區段 (需保留分號)
int k = 0;
for (; k < 5; ) { // 僅條件
Console.WriteLine(k++);
}
// 無限迴圈
for (;;) { /* 需 break 跳出 */ }
foreach 迴圈:遍歷集合適用於實作 IEnumerable/IEnumerable<T> 的型別:陣列、List、Dictionary、自訂集合。
int[] numbers = {1, 2, 3, 4, 5};
foreach (int n in numbers) {
Console.WriteLine(n);
}
// 唯讀:無法修改迭代變數
// n = 10; // 編譯錯誤
// 字典遍歷
var dict = new Dictionary { ["a"] = 1, ["b"] = 2 };
foreach (var (key, value) in dict) { // C# 7+ 解構
Console.WriteLine($"{key} = {value}");
}
// 索引需求:用 for 或 索引值元組
foreach (var (index, value) in numbers.Select((v, i) => (i, v))) { }
注意:foreach 對陣列/List 使用索引器,無列舉器配置開銷;對一般 IEnumerable 會建立列舉器。
while 迴圈:條件驅動int n = 10;
while (n > 0) {
Console.WriteLine(n--);
}
// 複雜條件
string? input;
while ((input = Console.ReadLine()) != "exit") {
Process(input);
}
// 旗標控制
bool running = true;
while (running) {
running = GameLoop();
}
do-while 迴圈:至少一次string? input;
do {
Console.Write("輸入指令: ");
input = Console.ReadLine();
Process(input);
} while (input != "quit");
// 選單系統經典用法
int choice;
do {
ShowMenu();
choice = ReadInt();
Execute(choice);
} while (choice != 0);
break:立即離開迴圈for (int i = 0; i < 100; i++) {
if (i == 42) break; // 只執行到 41
Console.WriteLine(i);
}
// 搜尋找到即停止
foreach (var item in items) {
if (item.Matches(criteria)) {
found = item;
break;
}
}
continue:跳過本次迭代,直接進入下一次判斷for (int i = 0; i < 10; i++) {
if (i % 2 == 0) continue; // 跳過偶數
Console.WriteLine(i); // 只印奇數 1,3,5,7,9
}
// 守衛子句風格
foreach (var file in files) {
if (!file.Exists) continue;
if (file.Size == 0) continue;
Process(file);
}
goto、return、throw 也可離開迴圈goto 可跳出多層巢狀迴圈(慎用),return 直接離開方法,throw 拋出例外。
// 雙重迴圈搜尋
outer:
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
if (matrix[i, j] == target) {
Console.WriteLine($"Found at ({i},{j})");
break outer; // 跳出外層迴圈
}
}
}
// 1. for(;;) - 慣用寫法
for (;;) {
var task = queue.Dequeue();
if (task == null) break;
task.Execute();
}
// 2. while(true) - 語意明確
while (true) {
if (cancellationToken.IsCancellationRequested) break;
DoWork();
}
// 3. 事件驅動迴圈
while (await channel.Reader.WaitToReadAsync()) {
while (channel.Reader.TryRead(out var item)) {
Process(item);
}
}
| 場景 | 建議 | 原因 |
|---|---|---|
| 陣列/List 索引存取 | for 迴圈 | 避免列舉器、邊界檢查消除、向量化 |
| 唯讀遍歷 | foreach | 語法簡潔、陣列/List 無配置、語意明確 |
| 需修改集合大小 | 反向 for 或 List.RemoveAll | 避免索引錯位、foreach 中修改拋例外 |
| 熱路徑大量迭代 | for + Span<T> | 零配置、SIMD 向量化、快取友善 |
| LINQ 鏈式 | 注意延遲執行、多次列舉 | 必要時 .ToArray() 具體化 |
// 自訂迴圈序列
IEnumerable Fibonacci(int count) {
int a = 0, b = 1;
for (int i = 0; i < count; i++) {
yield return a;
(a, b) = (b, a + b);
}
}
// 使用:延遲執行、串流處理
foreach (var n in Fibonacci(10).Take(5)) { } // 只產生前 5 個
for;遍歷集合 → foreach;條件驅動 → while;至少一次 → do-while。break/continue 簡化邏輯:守衛子句減少縮排層級。await foreach (IAsyncEnumerable)、Parallel.ForEachAsync。Batch/Chunk (NET 6+)。foreach 可以修改集合元素嗎?A: 迭代變數唯讀,但若元素為參考型別,可修改其內容(如 item.Name = "new")。不可修改集合結構(增/刪),否則拋 InvalidOperationException。
for vs foreach 效能差異?A: 陣列/List foreach 編譯器最佳化為索引存取,效能相近。一般 IEnumerable foreach 有列舉器配置。熱路徑基準測試為準。
A: 1) goto label 2) 提取方法用 return 3) 旗標變數 4) 例外 (不建議)。goto 在此場景可接受。