1.实例解析yiled的作用 近参加java笔试题第一次见到yield这个关键字,既然遇见了那肯定要掌握,下面是C#中关于yield关键字的总结。yield这个关键字作用于迭代器块中,其本质的功能有2个:一是“依次”向枚举对象提供值,二是发出迭代结束信号。这两个功能对应的语句分别是yield return和yield break。下面有2个小例子,分别没有使用yield和有使用yield。先来看第一个,当我调试时显然执行到GetResult()方法时将会跳转到方法内部并且执行完,接着再去执行输出当前值语句。从结果可以看出第一个是0,说明返回的枚举数所在的位置在集合中是0,接着才是我想要的遍历数据,也是说只有调用MoveNext()后枚举数才会继续向前移动得到下一个值,但是此时数据已全部加载到内存。再来看第二个例子,当我调试到GetResultByYield()方法时我想进入到这个方法内部结果发现直接执行下一句,根本没有进入到GetResultByYield()方法内部。此时发现result.Current是null,再加上前面根本都没执行方法内部的代码,因此我猜测此时集合都是空的。继续调试,当执行MoveNext()方法时才去执行GetResultByYield(),接着执行到yield return随即返回main()方法输出枚举数所代表的集合中的值。 从上面可以看到只有调用MoveNext()需要用的时候才去执行方法来获得结果,不用的时候并不会有任何结果。这个地方编译器会有一个状态机用来保存迭代器的状态,以保证for循环时是从上一次yield return停止的状态继续执行。这个过程好比小方要喝一升的水,如果它用一个一升的杯子喝那么他要准备一个一升的容器去饮水机装满一升的水。如果小方喝到一半喝不完了,那接下来剩下的水则将被回收,这样无论能不能喝完都必须准备好一升的水,像第一个例子。现在让杯子的容积缩小为0.2升,小方喝完一杯后再去饮水机去打水,每次只喝0.2升。这样只有他要去喝的时候才去打水,如果他喝到一半不想喝了显然浪费的水比第一种方式少,这像第二个例子。后根据条件不再需要数据便可调用yield return来跳出while循环,如果不写yield break仍然可以正常结束迭代。 /// <summary> /// 不使用yield的时候 /// </summary> class Program { static void Main(string[] args) { //得到一个迭代结果 var result = GetResult(); //输出当前的值 Console.WriteLine(result.Current); Console.WriteLine("开始遍历"); while (result.MoveNext()) { Console.WriteLine(result.Current); } Console.WriteLine("遍历结束"); Console.ReadLine(); } //不使用yield来进行迭代 static IEnumerator<int> GetResult() { var arr = new int[] { 1, 6, 8, 12,15}; List<int> list = new List<int>(); foreach (int item in arr) { if (item < 12) list.Add(item); } return list.GetEnumerator(); } } /// <summary> /// 使用yield关键字 /// </summary> class Program { static void Main(string[] args) { //得到一个迭代结果 var result = GetResultByYield(); //输出当前的值 Console.WriteLine(result.Current); Console.WriteLine("开始遍历"); while (result.MoveNext()) { Console.WriteLine(result.Current); } Console.WriteLine("遍历结束"); Console.ReadLine(); } //使用yield来进行迭代 static IEnumerator GetResultByYield() { var arr = new int[] { 1,6,8,12,15}; foreach (var item in arr) { yield return item; if (item == 12) yield break; } } } 输出结果如下:
2.深入yield 将上面第二个例子放入Reflector工具中,便得到了下面三段代码。第一段是完整的Pragrom类的C#代码,第二段是<GetResultByYield>d__0密封类的C#展开代码,第三段是GetResultByYield()方法的IL代码。在第一段代码中可以看到系统自动生成了一个<GetResultByYield>d__0密封类,它里面声明了一些名字很奇怪的字段,不过我们可以很清楚的看到这个类里面有重要的MoveNext()方法和Current属性。第二段代码则是这个密封类的C#展开代码,到这里不知道读者有没有和我当初一样的疑问:为什么要自动生成一个密封类呢?答案在第三段代码中,可以看到在GetResultByYield()方法中并没有遍历数组,甚至都没有看到创建数组的newarr指令,而是newobj创建了<GetResultByYield>d__0密封类的实例对象。这也正是前面调试的时候为什么根本没进去GetResultByYield()方法的原因,因为真真的实现代码是在密封类里面的MoveNext()方法中。前面还提到yield是按需所取,因此需要一个状态机来记录每次yield return的状态。