如果要返回一个其元素可供调用方逐个使用的序列,而无需首先在集合中生成完整的结果,请选择迭代器。 此方法适合处理生成的值、带有自定义控制流的筛选操作,以及调用方可能提前停止读取的序列。 与已完成的集合相比,迭代器改变的是工作发生的时机,而不只是代码的写法。
Tip
本文介绍面向了解方法、循环和集合的开发人员的同步迭代器。 有关完整的 yield 语法和限制,请参阅 yield 语句参考。
迭代器方法逐个生成值序列中的值。 本文重点介绍返回 IEnumerable<T> 的迭代器方法,调用方通常使用 foreach 语句来使用其值。
生成元素 yield return
使用 yield return 提供序列中的下一个元素。 以下迭代器从传感器中选择可接受的温度读数。 在此方案中,-1 标记着传感器数据的结束。
int[] readings = [21, 27, 24, -1, 19];
IEnumerable<int> acceptedReadings =
SelectTemperatures(readings, maximum: 25);
Console.WriteLine("Sequence created.");
foreach (int temperature in acceptedReadings)
{
Console.WriteLine($"Accepted: {temperature}°C");
}
static IEnumerable<int> SelectTemperatures(
IEnumerable<int> readings,
int maximum)
{
foreach (int reading in readings)
{
Console.WriteLine($"Checking {reading}°C");
if (reading == -1)
{
yield break;
}
if (reading <= maximum)
{
yield return reading;
}
}
}
SelectTemperatures 返回值 IEnumerable<int>,表示值序列 int 。 每个 yield return reading 都为 foreach 循环提供一个值。 调用方处理该值后,迭代器将继续处理下一个输入项。
对大多数新式 C# 代码使用通用 IEnumerable<T> 形式。 类型参数(如 int)告知调用方序列包含的元素类型。
以 yield break 结束该序列
使用 yield break 可在控制流到达方法末尾之前结束迭代器。 在此示例中,sentinel 值 -1 意味着没有更多可用读数:
if (reading == -1)
{
yield break;
}
当执行到达其正文末尾时,迭代器也会自然结束。 当条件应立即停止元素生产时使用 yield break 。
通过 消耗迭代器
迭代器公开与许多 .NET 集合相同的 IEnumerable<T> 结构。 调用方无需知道序列如何生成其元素即可使用 foreach :
foreach (int temperature in SelectTemperatures(readings, 25))
{
Console.WriteLine($"Accepted: {temperature}°C");
}
调用方在循环向前推进时请求每个元素。 迭代器在请求之间保持其位置,并在前一个 yield return之后继续。
了解延迟元素生成
调用迭代器方法不会立即运行其函数体。 当调用方开始枚举时,该方法开始生成元素。 此行为称为 延迟执行。
该示例会先打印 Checking ...,然后再打印任何 Sequence created. 消息。 仅当 foreach 循环请求其第一个元素时,迭代器才会开始检查读数。
延迟执行具有实际效果:
- 调用方可以在迭代器生成每个元素之前开始处理。
- 迭代器可以使用
yield break提前停止。 - 从不枚举序列的调用方不会运行迭代器正文。
- 每次新的遍历都会再次执行该迭代器。
在方法的用途和文档中明确延迟执行。 读者无法仅凭 IEnumerable<T> 推断执行时机,因为迭代器和集合都可以公开该接口。 使元素生成避免产生无关的副作用,因为调用方可能会稍后或多次枚举该序列。 该示例的诊断消息说明了该时序。
选择迭代器或集合
当值自然地逐个到达,或调用方不需要立即获得完整结果时,返回迭代器。 当调用方需要稳定的快照、索引访问或重复枚举而不重新计算元素时,返回完整的集合。
这两种方法都可以返回相同的元素。 迭代器将元素的生成交由调用方控制;生成集合的方法在返回之前完成该工作。
如果要对每个项执行操作而不返回序列,请使用普通循环。 对于标准筛选器或转换,首选 LINQ 操作,例如 Where 或 Select;当自定义元素生成逻辑作为一系列语句更清晰时,编写迭代器。
迭代器方法可与 LINQ 组合,因为两者都使用 IEnumerable<T>。 例如,调用方可以添加另一个筛选器:
var comfortableReadings = SelectTemperatures(readings, 25)
.Where(static temperature => temperature >= 18);
迭代器和 LINQ 操作将保持延迟执行,直到代码枚举 comfortableReadings 为止。
有关异步序列,请参阅 “生成和使用异步流”。 对于自定义枚举模式和所有 yield 限制,请使用 语言参考。