迴圈陳述式

Tip

本文屬於 基礎部分, 適合已經至少懂一種程式語言並正在學習 C# 的開發者。 如果你是程式新手,建議先從 入門 教學開始。 完整語法請參見語言參考中的 迭代陳述

來自另一種語言? 這四個 C# 迴圈(foreachwhiledo-whilefor和 )在 Java、C++ 和 JavaScript 中都有直接對應的。 你最常用的就是 foreach 。 它會在不使用索引的情況下疊代集合,就像 Java 的增強式 for 或 JavaScript 的 for...of 一樣。

迭代語句會重複執行一段程式碼。 每執行一次區塊就是一次 迭代,而重複執行的區塊就是 迴圈。 C# 提供四個迴圈。 針對集合,請從 foreach 開始;當條件控制重複時,請使用 whiledo-while;需要明確索引時,請使用 for

迭代一個集合 foreach

foreach 陳述式會依序對集合中的每個元素執行其主體一次。 這是讀取集合時最常見的選擇,因為你不必自行處理索引或進行邊界檢查。 此 foreach 語句防止典型的 off-by-one 錯誤:

string[] names = ["Ana", "Ben", "Cleo"];

// foreach reads each element in order. It's the default loop for
// collections: no index to manage and no off-by-one mistakes.
foreach (string name in names)
{
    Console.WriteLine(name); // => Ana, then Ben, then Cleo
}

foreach 適用於 C# 識別為序列的任何類型,包括陣列、 List<T>Dictionary<TKey,TValue>。 迭代變數(name 前一個例子中)是唯讀的,所以你無法在迴圈內重新指派它。

迴圈的主體是一個 單一陳述,例如指派或方法呼叫。 區塊陳述本身就是一個單一陳述,內含零個或多個陳述句的大括號({ })。

許多編碼標準建議即使只用一個陳述,也應該用大括號包圍迴圈主體。 牙套則能明確顯示範圍。 這可避免一個常見錯誤:之後又加入第二行,而且你原本預期它會在每次迭代時執行,但實際上它卻是在迴圈結束後才執行一次。 只有大括號決定哪些陳述屬於迴圈。 C# 不把留白當成重要,所以單純的縮排從來不會有意義。 即使只包含一行程式碼,大括號在語法上也是合法的:區塊就是迴圈重複執行的那個單一敘述。 縮排程式碼以提升可讀性,但要依賴大括號來定義區塊。

在條件成立時重複執行 while

while迴圈在每次迭代都會檢查其布林條件。 如果一開始條件是 false,迴圈主體就完全不會執行,因此 while 迴圈會執行零次或多次:

int countdown = 3;

// A while loop checks its condition before each iteration, so the body
// runs zero or more times.
while (countdown > 0)
{
    Console.WriteLine(countdown); // => 3, then 2, then 1
    countdown--;
}

確保迴圈內有東西改變了狀況。 在前一個例子中,最終 countdown-- 使條件成為 false。 一個條件永遠不會變成 false 的循環,永遠不停地運行。

至少使用 do-while 將主體執行一次

do - while迴圈會在每次迭代檢查其條件,因此迴圈主體至少會執行一次。 當必須先完成第一遍才能評估條件時,例如提示輸入並驗證時,請使用此方法:

int attempts = 0;

// A do-while loop runs its body once, then checks the condition. Use it
// when the body must run at least one time.
do
{
    attempts++;
    Console.WriteLine($"Attempt {attempts}"); // => Attempt 1, then Attempt 2, then Attempt 3
}
while (attempts < 3);

使用 for 計數

for 圈陳述包含三個部分:一個 初始化器 在迴圈前執行一次、一個在每次迭代前檢查的 條件 ,以及一個在每次迭代後執行的 迭代器 。 當你需要索引本身,而不只是元素時使用 for 。 通常,當你想修改元素時,需要索引,而不是讀取它的值。

// A for loop fits when you need an explicit index. The three parts are
// the initializer, the condition, and the iterator.
for (int i = 0; i < 3; i++)
{
    Console.WriteLine(i); // => 0, then 1, then 2
}

當你只閱讀元素,不使用位置或指派新值時,偏好 foreach。 它能更清楚地表達意圖,並避免指數運算。

使用 breakcontinue 退出或略過

兩個跳轉陳述式可讓你在任何迴圈內進行更精細的控制。 該 break 陳述會立即退出迴圈,跳過剩餘的迭代:

int[] numbers = [2, 4, 7, 8];

// break stops the loop immediately, skipping any remaining elements.
foreach (int number in numbers)
{
    if (number % 2 != 0)
    {
        Console.WriteLine($"First odd number: {number}"); // => First odd number: 7
        break;
    }
}

continue 陳述會跳過目前迭代的其餘部分,直接進入下一個迭代:

int[] values = [1, 2, 3, 4, 5];

// continue skips the rest of the current iteration and moves to the next.
foreach (int value in values)
{
    if (value % 2 == 0)
    {
        continue; // skip even numbers
    }

    Console.WriteLine(value); // => 1, then 3, then 5
}

迭代一個非同步串流 await foreach

非同步串流是一種讀取器,利用非同步任務來產生下一個元素。 C# 用 IAsyncEnumerable<T> 介面來表示。 隨時間累積的資料,例如來自網頁 API 的頁面或資料庫的列,符合此模型:取得下一個元素是一個等待的操作,而非立即回傳。

若要取用非同步串流,請將 await 關鍵字置於 foreach 之前。 每次迭代都等待下一個元素,因此迴圈在產生該元素時會暫停,而不是阻塞執行緒:

private static async Task AwaitForeachExample()
{
    // await foreach consumes an asynchronous stream. Each iteration can
    // suspend while the next element is produced.
    await foreach (int value in GenerateAsync())
    {
        Console.WriteLine(value); // => 0, then 1, then 2
    }
}

private static async IAsyncEnumerable<int> GenerateAsync()
{
    for (int i = 0; i < 3; i++)
    {
        await Task.Delay(1); // stand-in for real asynchronous work
        yield return i;
    }
}

非同步串流則建立在 asyncawait之上。 完整攻略請參見 「產生並消費非同步串流」。

參見