迭代器学习之四:关于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查看!

 

  好了,关于迭代的学习结束了,本来是做的,然后写到博客中去的,在这个过程中又对迭代器有了进一步的认识,所以经常写写,练练,想想,应该就不会太难了!

  文章不一定很完善,需要园子里面的朋友指导!

  在学习中深入,在实践中提高,是我的理念!坚持,努力,是我的性格!

posted @ 2011-12-11 16:57 TimYang 阅读(...) 评论(...) 编辑 收藏