迭代语句

Tip

本文是已了解至少一种编程语言并正在学习 C# 的开发人员的 “基础知识 ”部分的一部分。 如果你不熟悉编程,请先 学习入门 教程。 有关完整语法,请参阅语言参考中的 迭代语句

来自另一种语言? 所有四个 C# 循环(foreachwhiledo-whilefor)在 Java、C++ 和 JavaScript 中具有直接等效项。 你最常用 foreach。 它在不使用索引的情况下遍历集合,类似于 Java 的增强型 for 或 JavaScript 的 for...of

迭代语句重复运行代码块。 每个遍历块都是一次 迭代,重复块是一个 循环。 C# 提供四个循环。 对于集合,首先使用foreach;当重复由条件控制时,使用whiledo-while;当需要显式索引时,使用for

使用 foreach 遍历集合

foreach 语句按顺序对集合中的每个元素运行其正文一次。 这是读取集合的最常见选择,因为你不管理索引或边界检查。 该 foreach 语句可防止典型的一位偏移错误:

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> 接口来表示它。 随着时间的推移到达的数据(如 Web 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 之上。 有关完整演练,请参阅 生成和使用异步流

另见