解读C#解读 --- yield 关键字
Fantasy-ke引言
yield关键字是 C# 中的一种语言特性,用于在枚举器中简化迭代器的实现。它使得开发人员可以通过定义自己的迭代器来简化代码,而不必手动实现 IEnumerable 和 IEnumerator 接口。
使用 yield 关键字,可以将迭代器中的值一次一个地返回,而不必使用一个集合对象存储所有的值。当执行到yield return语句时,代码将会暂停执行,将返回值传递给迭代器的调用者,并将迭代器的状态保存下来。当下一次调用MoveNext方法时,代码将从之前的暂停点继续执行,直到遇到下一个yield return语句或者迭代器结束。
接下来探索一下 yield 的三种玩法:
初级
例如通过 yield 创建出一个 IEnumerable 以供 foreach 遍历,代码如下:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 
 | internal class Program{
 static void Main(string[] args)
 {
 foreach (int i in EvenSequence(5, 18))
 {
 Console.Write(i + " ");
 }
 
 Console.ReadKey();
 }
 
 static IEnumerable<int> EvenSequence(int start, int end)
 {
 for (int i = start; i <= end; i++)
 {
 if (i % 2 == 0)
 {
 yield return i;
 }
 }
 }
 }
 
 | 
在上面的代码中,我们定义了一个名为EvenSequence的方法,它返回一个实现了IEnumerable<int>接口的对象。在EvenSequence方法中,我们使用yield return语句来返回每个偶数值,并在每次暂停后保存方法的状态。
在Main方法中,我们使用foreach循环语句来遍历EvenSequence方法返回的集合对象,并输出每个偶数值。由于我们使用了 yield 关键字,即使我们没有显式地实现IEnumerable和IEnumerator接口,也能够遍历集合对象。
进阶
另一个方面,异步编程中也常常使用yield来创建异步生成器。使用yield创建异步生成器可以让我们轻松地以异步方式生成一系列值,而无需显式地管理异步状态。如以下代码:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 
 | public class Program{
 public static void Main(string[] args)
 {
 AsynchronousIterate();
 
 Console.ReadLine();
 }
 
 public static async void AsynchronousIterate()
 {
 await foreach (var number in GenerateNumbersAsync())
 {
 Console.WriteLine(number);
 }
 }
 
 
 public static async IAsyncEnumerable<int> GenerateNumbersAsync()
 {
 for (int i = 0; i < 10; i++)
 {
 await Task.Delay(1000);
 
 yield return i;
 }
 }
 
 }
 
 | 
上述代码定义了一个名为 GenerateNumbersAsync 的异步方法,该方法返回一个 IAsyncEnumerable<int> 类型的对象。在方法体中,我们使用一个 for 循环来生成一系列整数,并在每次迭代中异步等待1秒钟。紧接着,我们使用 yield return 语句将生成的整数返回给调用方。 要注意调用时使用异步迭代器(具有 await 关键字的foreach)来进行遍历>。
上述输出则是一秒输出一个结果,知道全部输出。
进阶举例场景
现在有这一样一个场景,有一大缸水,你手里有一个水瓢,,现在需要你把水缸里的水,全部移到另一个水缸。
前提条件:现在不知道缸里由具体多少水,也无法确定一瓢能舀多少,也无法确定你每一次舀水操作需要多长时间。
现在写一段代码,模拟这个过程。
拟定前提条件,如下代码:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 
 | static Random Random = new Random();
 
 
 
 public static int GetOneWaterTankCapacity()
 {
 return Random.Next(150, 200);
 }
 
 
 
 
 public static int GetOneWaterLadleCapacity()
 {
 return Random.Next(2, 5);
 }
 
 
 
 
 
 public static async Task<int> ScoopingWater()
 {
 await Task.Delay(Random.Next(500, 2000));
 return GetOneWaterLadleCapacity();
 }
 
 | 
开始舀水,代码如下:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 
 | 
 
 
 public static async void MoveWater()
 {
 
 var WaterTankCapacity = GetOneWaterTankCapacity();
 
 await foreach (var item in CreateTasks(WaterTankCapacity))
 {
 Console.WriteLine($"这一瓢舀水量:{item.Result}\t{DateTime.Now}");
 }
 Console.WriteLine($"水全部舀完~\t{DateTime.Now}");
 }
 
 private static async IAsyncEnumerable<Task<int>> CreateTasks(int waterTankCapacity)
 {
 int totle = 0;
 while (totle < waterTankCapacity)
 {
 Task<int> someWater = ScoopingWater();
 
 yield return someWater;
 
 totle += await someWater;
 }
 }
 
 | 
最终会每次随机舀水,花费随机时间,水全部舀完。
输出:
| 12
 3
 4
 5
 6
 7
 8
 9
 
 | 4 2023/5/11 23:06:29这一瓢舀水量:3 2023/5/11 23:06:30
 这一瓢舀水量:2 2023/5/11 23:06:32
 这一瓢舀水量:2 2023/5/11 23:06:34
 这一瓢舀水量:3 2023/5/11 23:06:35
 这一瓢舀水量:4 2023/5/11 23:06:36
 这一瓢舀水量:2 2023/5/11 23:06:37
 ....
 水全部舀完~     2023/5/11 23:07:27
 
 | 
通过上文例子,可以更深入理解 yield 创建的异步生成器。
总结
- 使用 yield关键字可以将一个方法转换为一个返回可枚举对象或迭代器的方法,而不必手动实现 IEnumerable 和 IEnumerator 接口
- 使用 yield来创建异步生成器,在某些场景下可以实现更高效、可靠的异步编程。