迭代器学习之四:关于yield的深入了解
在前面的三篇文章中已经对IEnumerable和IEnumerator的知识做了很多的讲解了!
但是在.NET2.0以后却提供了一个更为简单的创建可枚举类型和枚举数,这是由手动 → 编译器内置迭代器自动生成可枚举类型和枚举数,他就是“yield”,它更加的简化了迭代器的使用,提高了开发人员的效率,那就让我来说一说它!
本文注重yield的内部实现,至于用法不加详细说明,需要的话网上搜搜一大堆!
对于yield园子里面也有很多经典的文章,写这篇只是记录学习时候的感受,纯属一家之言啊!
1.yield
简单例子:
1 public class MyIterator
2 {
3 public IEnumerable<string> GetMyIEnumerable() //最终返回的是个数组
4 {
5 yield return "Red";
6
7 yield return "Block";
8
9 yield return "Yellow";
10
11 }
12
13 //请记住一点,如果一个类中没有实现“GetEnumerator”方法,且返回值类型为IEnumerator的泛型或非泛型,那么就不是可枚举类型,就不能使用“foreach”
14 public IEnumerator<string> GetEnumerator()
15 {
16 IEnumerable<string> colors = GetMyIEnumerable(); //此时的colors数组是IEnumerable<string>类型,所以下面要转化下
17 return colors.GetEnumerator(); //数组调用自己的GetEnumerator方法,返回IEnumerator类型!
18 //return GetMyIEnumerable().GetEnumerator(); //还可以这样写
19 }
20 }
客户端调用:
1 static void Main(string[] args)
2 {
3 MyIterator my = new MyIterator();
4 foreach (var item in my)
5 {
6 Console.WriteLine(item);
7 }
9 }
结果:大家应该都能想到,就是遍历数组元素并一一输出在控制台上!
乍一看怎么这么简单就实现了可枚举类型和枚举数,前几篇写了那么多的代码才实现了这两个功能,这边几行就搞定了,不急,慢慢来探究!
Note:当一个类型被foreach时,它会检查你的类中是否有“GetEnumerator”方法,然后调用方法返回枚举数,通过返回的“IEnumerator”对象中包含的引用找出要枚举的类型,然后调用它里面实现IEnumerator接口的两个方法和一个属性,进行遍历!
2.查看yield内部的实现过程(Reflector或IL代码)
编译代码,通过Reflector查看内部实现代码:
做几点说明:
① 编译器在遇到“yield”关键的时候就会自动生成一个嵌套类做为枚举数
② 上面的示例代码也可以写为:
1 public class MyIteratorTwo
2 {
3 public IEnumerator<string> GetEnumerator()
4 {
6 yield return "Red";
7
8 yield return "Block";
9
10 ield return "Yellow";
12 }
13 }
③ ★编译器的编译的时候在检测到“yield”关键字之后就帮我们自动生成了枚举数类,最后在运行的时候在执行到“yield return "Red";”,就会调用自动生成的类中的Current属性返回当前的项,循环读取每一项!
④ 编译器生成的内部代码实现的很完美,包括在定义,在读取的时候都设计的很不错!
⑤ 通过Reflector可以看出在调用 GetEnumerator方法时,会实例化一个枚举数的类型实例(这个实例化的枚举数类型就是编译器自动帮我们生成的枚举数类型),然后在构造函数中传递一个int类型的state的变量,值为“0”!
⑤ 关于其它方法或属性的实现过程可以通过Reflector查看,应该很容易看懂的!
⑦ 下面为IL的代码图:
其实IL中的编译代码也很容易的看出编译器为我们做的那些事!
⑦ 最后引用书中的一个图加以说明:
⑧ yield迭代器无非就是帮我们生成了一系列的代码,大体上跟我们自己定义的可枚举类型和枚举数差不多,唯一不变的是编译器生成的代码比我们自己写的要好点!
3.yield return 和 yield break的认识
① yield return 返回下一个迭代,并且会保存当前项的位置(其实这个位置保存自动生成的类中一个全局变量中)
② yield break 迭代结束。说明我不再需要迭代了,告诉foreach遍历了结束了!
4.关于迭代器的一些额外话
① yield return 一个重要的功能是他并不需要把你要读取的数据全部装入迭代中,才开始一个一个的返回,它是取到一个数据立即返回数据的值,大大提高了性能,也就是当读到yield return时就去调用自动生成的枚举数类,返回当前的值,然后再去读取下一个yield return!
② 迭代器会在我们不需要实现IEnumerator和IEnumerable接口的情况下就可以实现foreach的遍历,而那些实现接口的事编译器会帮我们做的!
③ 其实迭代器就是封装了实现IEnumerable和IEnumerator接口的过程,通过yield这个关键字编译器会通过它的一套算法功能,自动帮我们实现接口!
④ 在编译器自动生成的枚举数中,Reset方法没有实现,但它是接口需要的方法,所以它里面的代码实现是抛出一个异常,可以通过reflector查看!
好了,关于迭代的学习结束了,本来是做的,然后写到博客中去的,在这个过程中又对迭代器有了进一步的认识,所以经常写写,练练,想想,应该就不会太难了!
文章不一定很完善,需要园子里面的朋友指导!
在学习中深入,在实践中提高,是我的理念!坚持,努力,是我的性格!