在yield使用 语句提供下一个值或表示迭代结束。
yield 语句有以下两种形式:
yield return:在迭代中提供下一个值,如以下示例所示:foreach (int i in ProduceEvenNumbers(9)) { Console.Write(i); Console.Write(" "); } // Output: 0 2 4 6 8 IEnumerable<int> ProduceEvenNumbers(int upto) { for (int i = 0; i <= upto; i += 2) { yield return i; } }yield break:显式示迭代结束,如以下示例所示:Console.WriteLine(string.Join(" ", TakeWhilePositive(new int[] {2, 3, 4, 5, -1, 3, 4}))); // Output: 2 3 4 5 Console.WriteLine(string.Join(" ", TakeWhilePositive(new int[] {9, 8, 7}))); // Output: 9 8 7 IEnumerable<int> TakeWhilePositive(IEnumerable<int> numbers) { foreach (int n in numbers) { if (n > 0) { yield return n; } else { yield break; } } }当控件到达迭代器的末尾时,迭代也结束。
在前面的示例中,迭代器的返回类型为 IEnumerable<T>(在非泛型情况下,使用 IEnumerable 作为迭代器的返回类型)。 还可以使用 IAsyncEnumerable<T> 作为迭代器的返回类型。 这使得迭代器异步。 使用 await foreach 语句对迭代器的结果进行迭代,如以下示例所示:
await foreach (int n in GenerateNumbersAsync(5))
{
Console.Write(n);
Console.Write(" ");
}
// Output: 0 2 4 6 8
async IAsyncEnumerable<int> GenerateNumbersAsync(int count)
{
for (int i = 0; i < count; i++)
{
yield return await ProduceNumberAsync(i);
}
}
async Task<int> ProduceNumberAsync(int seed)
{
await Task.Delay(1000);
return 2 * seed;
}
迭代器的返回类型可以是 IEnumerator<T> 或 IEnumerator。 在以下方案中实现 GetEnumerator 方法时,请使用这些返回类型:
设计实现 IEnumerable<T> 或 IEnumerable 接口的类型。
添加实例或扩展
GetEnumerator方法来使用foreach语句对类型的实例启用迭代,如以下示例所示:public static void Example() { var point = new Point(1, 2, 3); foreach (int coordinate in point) { Console.Write(coordinate); Console.Write(" "); } // Output: 1 2 3 } public readonly record struct Point(int X, int Y, int Z) { public IEnumerator<int> GetEnumerator() { yield return X; yield return Y; yield return Z; } }
不能在下列情况中使用 yield 语句:
- 带有 in、ref 或 out 参数的方法。
- Lambda 表达式和匿名方法
-
不安全块。 在 C# 13 之前,
yield在具有unsafe块的任何方法中都无效。 从 C# 13 开始,可以在包含yield块的方法中使用unsafe,但不能在unsafe块中使用。 -
yield returnyield break不能在 catch 和 finally 块中使用,也不能在具有相应块的 trycatch。 和yield returnyield break语句可以在没有try块的块(只有块catch)的块中使用finally。
using 迭代器中的语句
可以在迭代器方法中使用 using 语句 。 由于 using 语句编译为 try 包含 finally 子句的块(且没有 catch 块),因此它们能够正确处理迭代器。 可释放资源在整个迭代器执行过程中得到适当的管理:
Console.WriteLine("=== Using in Iterator Example ===");
// Demonstrate that using statements work correctly in iterators
foreach (string line in ReadLinesFromResource())
{
Console.WriteLine($"Read: {line}");
// Simulate processing only first two items
if (line == "Line 2") break;
}
Console.WriteLine("Iteration stopped early - resource should still be disposed.");
static IEnumerable<string> ReadLinesFromResource()
{
Console.WriteLine("Opening resource...");
using var resource = new StringWriter(); // Use StringWriter as a simple IDisposable
resource.WriteLine("Resource initialized");
// These lines would typically come from the resource (e.g., file, database)
string[] lines = { "Line 1", "Line 2", "Line 3", "Line 4" };
foreach (string line in lines)
{
Console.WriteLine($"About to yield: {line}");
yield return line;
Console.WriteLine($"Resumed after yielding: {line}");
}
Console.WriteLine("Iterator completed - using block will dispose resource.");
}
如前面的示例所示,在语句中 using 获取的资源在整个迭代器执行过程中仍然可用,即使迭代器暂停并在语句中 yield return 恢复执行。 当迭代器完成(通过到达结束或通过 yield break)或释放迭代器本身时(例如,当调用方提前中断枚举时),将释放资源。
迭代器的执行
迭代器的调用不会立即执行,如以下示例所示:
var numbers = ProduceEvenNumbers(5);
Console.WriteLine("Caller: about to iterate.");
foreach (int i in numbers)
{
Console.WriteLine($"Caller: {i}");
}
IEnumerable<int> ProduceEvenNumbers(int upto)
{
Console.WriteLine("Iterator: start.");
for (int i = 0; i <= upto; i += 2)
{
Console.WriteLine($"Iterator: about to yield {i}");
yield return i;
Console.WriteLine($"Iterator: yielded {i}");
}
Console.WriteLine("Iterator: end.");
}
// Output:
// Caller: about to iterate.
// Iterator: start.
// Iterator: about to yield 0
// Caller: 0
// Iterator: yielded 0
// Iterator: about to yield 2
// Caller: 2
// Iterator: yielded 2
// Iterator: about to yield 4
// Caller: 4
// Iterator: yielded 4
// Iterator: end.
如前面的示例所示,当开始对迭代器的结果进行迭代时,迭代器会一直执行,直到到达第一个 yield return 语句为止。 然后,迭代器的执行会暂停,调用方会获得第一个迭代值并处理该值。 在后续的每次迭代中,迭代器的执行都会在导致上一次挂起的 yield return 语句之后恢复,并继续执行,直到到达下一个 yield return 语句为止。 当控件到达迭代器或 yield break 语句的末尾时,迭代完成。
C# 语言规范
有关详细信息,请参阅 C# 语言规范中的 yield 语句部分。