作用
在迭代器中返回下一个元素或者表示迭代结束
两种形式
1. yield return 表示返回迭代器下一个元素
写一个方法,求 2 3 2^{3} 23,分别用yield和普通的写法,来看一下区别
// yield 写法
static IEnumerable<int> Power1(int num, int exponet)
{
int result = 1;
Console.WriteLine("yield 循环开始.");
for (int i = 0; i < exponet; i++)
{
Console.WriteLine($"将要 yield {i}");
result *= num;
yield return result;
Console.WriteLine($" 已经yield {i}");
}
Console.WriteLine("yield 循环结束.");
}
// 普通写法
static IEnumerable<int> Power2(int num, int exponet)
{
List<int> list = new List<int>();
int result = 1;
Console.WriteLine("普通循环开始.");
for (int i = 0; i < exponet; i++)
{
Console.WriteLine($"将要循环 {i}");
result *= num;
list.Add(result);
Console.WriteLine($"已经循环 {i}");
}
Console.WriteLine("普通循环结束.");
return list;
}
调用方法
#region yield return 返回下一个元素
IEnumerable<int> result4 = Power1(2, 4);
Console.WriteLine("hahah");
foreach (var item in result4)
{
Console.WriteLine($"yield计算结果:{item}");
}
Console.WriteLine();
IEnumerable<int> result5 = Power2(2, 4);
Console.WriteLine("lalala");
foreach (var item in result5)
{
Console.WriteLine($"普通计算结果---:{item}");
}
Console.ReadKey();
#endregion
如结果所示,当开始对迭代器的结果进行迭代时,迭代器会一直执行,直到到达第一个 yield return 语句为止。 然后,迭代器的执行会暂停,调用方会获得第一个迭代值并处理该值。 在后续的每次迭代中,迭代器的执行都会在导致上一次挂起的 yield return 语句之后恢复,并继续执行,直到到达下一个 yield return 语句为止。 当控件到达迭代器或 yield break 语句的末尾时,迭代完成。
这里有个需要注意的点:power1()方法并不是立即被调用的。当代码执行到 IEnumerable<int> result4 = Power1(2, 4);
时,并没有立刻调用Power1,而是到了下面的for循环时才调用的,这一点和普通写法的调用顺序是不一样的。普通写法时,当代码执行到 IEnumerable<int> result5 = Power2(2, 4);
时会立即调用Power2方法。这一点可以通过运行结果里面打印hahaha和lalala的顺序直观的看出来。
2. yield break 表示迭代结束
#region yield break 迭代结束
Console.WriteLine(string.Join(" ", GetInts(new int[] { 1, 3, -5, 9 })));
Console.WriteLine(string.Join(" ", GetInts(new int[] { 22, 44, 6, 8 })));
#endregion
由上图可知,yield可以用于显示结束迭代。yield并不影响迭代的正常结束,当迭代到达末尾时,也可结束。
返回值类型
1. IEnumerable<T>
迭代器的返回类型为 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;
}
2. IEnumerator<T>
迭代器的返回类型可以是 IEnumerator 或 IEnumerator。 在以下场景中实现 GetEnumerator 方法时,这非常有用:
-
设计实现 IEnumerable 或 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;
}
}
不可用情况
- 带有 in、ref 或 out 参数的方法
- Lambda 表达式和匿名方法
- 包含不安全的块的方法