# [C#基础知识系列]专题十二:迭代器
**引言:**
在C# 1.0中我们经常使用foreach来遍历一个集合中的元素,然而**一个类型要能够使用foreach关键字来对其进行遍历必须实现IEnumerable****或IEnumerable<T>接口**,(之所以来必须要实现**IEnumerable**这个接口,是因为foreach是迭代语句,要使用foreach必须要有一个迭代器才行的,然而IEnumerable接口中就有**IEnumerator GetEnumerator()**方法是返回迭代器的,所以实现了**IEnumerable**接口,就必须实现GetEnumerator()这个方法来返回迭代器,有了迭代器就自然就可以使用foreach语句了),然而在C# 1.0中要获得迭代器就必须实现**IEnumerable**接口中的****GetEnumerator()****方法,然而要实现一个迭代器就必须实现**IEnumerator**接口中的bool MoveNext()和void Reset()方法,然而 C# 2.0中提供 **yield**关键字来简化迭代器的实现,这样在C# 2.0中如果我们要自定义一个迭代器就容易多了。下面就具体介绍了C# 2.0 中如何提供对迭代器的支持.
**一、迭代器的介绍**
迭代器大家可以想象成数据库的游标,即一个集合中的某个**位置**,C# 1.0中使用foreach语句实现了访问迭代器的内置支持,使用foreach使我们遍历集合更加容易(比使用for语句更加方便,并且也更加容易理解),foreach被编译后会调用GetEnumerator来返回一个迭代器,也就是一个集合中的初始位置(foreach其实也相当于是一个语法糖,把复杂的生成代码工作交给编译器去执行)。
**二、C#1.0如何实现迭代器**
在C# 1.0 中实现一个迭代器必须实现**IEnumerator**接口,下面代码演示了传统方式来实现一个自定义的迭代器:
```
1 using System;
2 using System.Collections;
3
4 namespace 迭代器Demo
5 {
6 class Program
7 {
8 static void Main(string[] args)
9 {
10 Friends friendcollection = new Friends();
11 foreach (Friend f in friendcollection)
12 {
13 Console.WriteLine(f.Name);
14 }
15
16 Console.Read();
17 }
18 }
19
20 /// <summary>
21 /// 朋友类
22 /// </summary>
23 public class Friend
24 {
25 private string name;
26 public string Name
27 {
28 get { return name; }
29 set { name = value; }
30 }
31 public Friend(string name)
32 {
33 this.name = name;
34 }
35 }
36
37 /// <summary>
38 /// 朋友集合
39 /// </summary>
40 public class Friends : IEnumerable
41 {
42 private Friend[] friendarray;
43
44 public Friends()
45 {
46 friendarray = new Friend[]
47 {
48 new Friend("张三"),
49 new Friend("李四"),
50 new Friend("王五")
51 };
52 }
53
54 // 索引器
55 public Friend this[int index]
56 {
57 get { return friendarray[index]; }
58 }
59
60 public int Count
61 {
62 get { return friendarray.Length; }
63 }
64
65 // 实现IEnumerable<T>接口方法
66 public IEnumerator GetEnumerator()
67 {
68 return new FriendIterator(this);
69 }
70 }
71
72 /// <summary>
73 /// 自定义迭代器,必须实现 IEnumerator接口
74 /// </summary>
75 public class FriendIterator : IEnumerator
76 {
77 private readonly Friends friends;
78 private int index;
79 private Friend current;
80 internal FriendIterator(Friends friendcollection)
81 {
82 this.friends = friendcollection;
83 index = 0;
84 }
85
86 #region 实现IEnumerator接口中的方法
87 public object Current
88 {
89 get
90 {
91 return this.current;
92 }
93 }
94
95 public bool MoveNext()
96 {
97 if (index + 1 > friends.Count)
98 {
99 return false;
100 }
101 else
102 {
103 this.current = friends[index];
104 index++;
105 return true;
106 }
107 }
108
109 public void Reset()
110 {
111 index = 0;
112 }
113
114 #endregion
115 }
116 }
```
运行结果(上面代码中都有详细的注释,这里就不说明了,直接上结果截图):
![](https://box.kancloud.cn/2016-01-23_56a2eb2bac041.png)
**三、使用C#2.0的新特性简化迭代器的实现**
在C# 1.0 中要实现一个迭代器必须实现**IEnumerator**接口,这样就必须实现**IEnumerator**接口中的MoveNext、Reset方法和Current属性,从上面代码中看出,为了实现FriendIterator迭代器需要写40行代码,然而在C# 2.0 中通过yield return语句简化了迭代器的实现,下面看看C# 2.0中简化迭代器的代码:
```
1 namespace 简化迭代器的实现
2 {
3 class Program
4 {
5 static void Main(string[] args)
6 {
7 Friends friendcollection = new Friends();
8 foreach (Friend f in friendcollection)
9 {
10 Console.WriteLine(f.Name);
11 }
12
13 Console.Read();
14 }
15 }
16
17 /// <summary>
18 /// 朋友类
19 /// </summary>
20 public class Friend
21 {
22 private string name;
23 public string Name
24 {
25 get { return name; }
26 set { name = value; }
27 }
28 public Friend(string name)
29 {
30 this.name = name;
31 }
32 }
33
34 /// <summary>
35 /// 朋友集合
36 /// </summary>
37 public class Friends : IEnumerable
38 {
39 private Friend[] friendarray;
40
41 public Friends()
42 {
43 friendarray = new Friend[]
44 {
45 new Friend("张三"),
46 new Friend("李四"),
47 new Friend("王五")
48 };
49 }
50
51 // 索引器
52 public Friend this[int index]
53 {
54 get { return friendarray[index]; }
55 }
56
57 public int Count
58 {
59 get { return friendarray.Length; }
60 }
61
62 // C# 2.0中简化迭代器的实现
63 public IEnumerator GetEnumerator()
64 {
65 for (int index = 0; index < friendarray.Length; index++)
66 {
67 // 这样就不需要额外定义一个FriendIterator迭代器来实现IEnumerator
68 // 在C# 2.0中只需要使用下面语句就可以实现一个迭代器
69 yield return friendarray[index];
70 }
71 }
72 }
73 }
```
在上面代码中有一个**yield return** 语句,这个语句的作用就是告诉编译器GetEnumerator方法不是一个普通的方法,而是实现一个迭代器的方法,当编译器看到**yield return**语句时,编译器知道需要实现一个迭代器,所以编译器生成中间代码时为我们生成了一个**IEnumerator**接口的对象,大家可以通过Reflector工具进行查看,下面是通过Reflector工具得到一张截图:
![](https://box.kancloud.cn/2016-01-23_56a2eb2bb92de.jpg)
从上面截图可以看出,**yield return** 语句其实是C#中提供的另一个语法糖,简化我们实现迭代器的源代码,把具体实现复杂迭代器的过程交给编译器帮我们去完成,看来C#编译器真是做得非常人性化,把复杂的工作留给自己做,让我们做一个简单的工作就好了。
**四、迭代器的执行过程**
为了让大家更好的理解迭代器,下面列出迭代器的执行流程:
![](https://box.kancloud.cn/2016-01-23_56a2eb2bd5ec4.png)
**五、迭代器的延迟计算**
从第四部分中迭代器的执行过程中可以知道迭代器是延迟计算的, 因为迭代的主体在MoveNext()中实现(因为在MoveNext()方法中访问了集合中的当前位置的元素),Foreach中每次遍历执行到in的时候才会调用MoveNext()方法,所以迭代器可以延迟计算,下面通过一个示例来演示迭代器的延迟计算:
```
namespace 迭代器延迟计算Demo
{
class Program
{
/// <summary>
/// 演示迭代器延迟计算
/// </summary>
/// <param name="args"></param>
static void Main(string[] args)
{
// 测试一
//WithIterator();
//Console.Read();
// 测试二
//WithNoIterator();
//Console.Read();
// 测试三
foreach (int j in WithIterator())
{
Console.WriteLine("在main输出语句中,当前i的值为:{0}", j);
}
Console.Read();
}
public static IEnumerable<int> WithIterator()
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine("在WithIterator方法中的, 当前i的值为:{0}", i);
if (i > 1)
{
yield return i;
}
}
}
public static IEnumerable<int> WithNoIterator()
{
List<int> list = new List<int>();
for (int i = 0; i < 5; i++)
{
Console.WriteLine("当前i的值为:{0}", i);
if (i > 1)
{
list.Add(i);
}
}
return list;
}
}
}
```
当运行测试一的代码时,控制台中什么都不输出,原因是生成的迭代器延迟了**i** 值的输出,大家可以用Reflector工具反编译出编译器生成的中间语言代码就可以发现原因了,下面是一张截图:
![](https://box.kancloud.cn/2016-01-23_56a2eb2bed423.jpg)
从图中可以看出,WithIterator()被编译成下面的代码了(此时编译器把我们自己方法体写的代码给改了):
从而当我们测试一的代码中调用WithIterator()时,对于编译器而言,就是实例化了一个**<WithIterator>d_0**的对象(<WithIterator>d_0类是编译看到WithIterator方法中包含Yield return 语句生成的一个迭代器类),所以运行测试一的代码时,控制台中什么都不输出。
当运行测试二的代码时,运行结果就如我们期望的那样输出(这里的运行结果就不解释了,列出来是为了更好说明迭代器的延迟计算):
![](https://box.kancloud.cn/2016-01-23_56a2eb2c0f5cb.jpg)
当我们运行测试三的代码时,运行结果就有点让我们感到疑惑了, 下面先给出运行结果截图,然后在分析原因。
![](https://box.kancloud.cn/2016-01-23_56a2eb2c1e3c5.jpg)
可能刚开始看到上面的结果很多人会有疑问,为什么2,3,4会运行两次的呢?下面具体为大家分析下为什么会有这样的结果。
测试代码三中通过foreach语句来遍历集合时,当运行in的时候就会运行IEnumerator.MoveNext()方法,下面是上面代码的MoveNext()方法的代码截图:
![](https://box.kancloud.cn/2016-01-23_56a2eb2c31388.jpg)
从截图中可以看到有Console.WriteLine()语句,所以用foreach遍历的时候才会有结果输出(主要是因为foreach中in 语句调用了MoveNext()方法),至于为什么2,3,4会运行两行,主要是因为这里有两个输出语句,一个是WithIterator方法体内for语句中的输出语句,令一个是Main函数中对WithIterator方法返回的集合进行迭代的输出语句,在代码中都有明确指出,相信大家经过这样的解释后就不难理解测试三的运行结果了。
**六、小结**
本专题主要介绍了C# 2.0中通过**yield return语句**对迭代器实现的简化,然而对于编译器而言,却没有简化,它同样生成了一个类去实现IEnumerator接口,只是我们开发人员去实现一个迭代器得到了简化而已。希望通过本专题,大家可以对迭代器有一个进一步的认识,并且迭代器的延迟计算也是Linq的基础,本专题之后将会和大家介绍C# 3.0中提出的新特性,然而C# 3.0中提出来的Lambda,Linq可以说是彻底改变我们编码的风格,后面的专题中将会和大家一一分享我所理解C# 3.0 中的特性。
附件:源程序代码:[http://files.cnblogs.com/zhili/%E8%BF%AD%E4%BB%A3%E5%99%A8Demo.zip](http://files.cnblogs.com/zhili/%E8%BF%AD%E4%BB%A3%E5%99%A8Demo.zip)
破解版的Reflector工具:[http://files.cnblogs.com/zhili/Reflector.zip](http://files.cnblogs.com/zhili/Reflector.zip)
- C# 基础知识系列
- C# 基础知识系列 专题一:深入解析委托——C#中为什么要引入委托
- C# 基础知识系列 专题二:委托的本质论
- C# 基础知识系列 专题三:如何用委托包装多个方法——委托链
- C# 基础知识系列 专题四:事件揭秘
- C# 基础知识系列 专题五:当点击按钮时触发Click事件背后发生的事情
- C# 基础知识系列 专题六:泛型基础篇——为什么引入泛型
- C# 基础知识系列 专题七: 泛型深入理解(一)
- C# 基础知识系列 专题八: 深入理解泛型(二)
- C# 基础知识系列 专题九: 深入理解泛型可变性
- C#基础知识系列 专题十:全面解析可空类型
- C# 基础知识系列 专题十一:匿名方法解析
- C#基础知识系列 专题十二:迭代器
- C#基础知识 专题十三:全面解析对象集合初始化器、匿名类型和隐式类型
- C# 基础知识系列 专题十四:深入理解Lambda表达式
- C# 基础知识系列 专题十五:全面解析扩展方法
- C# 基础知识系列 专题十六:Linq介绍
- C#基础知识系列 专题十七:深入理解动态类型
- 你必须知道的异步编程 C# 5.0 新特性——Async和Await使异步编程更简单
- 全面解析C#中参数传递
- C#基础知识系列 全面解析C#中静态与非静态
- C# 基础知识系列 C#中易混淆的知识点
- C#进阶系列
- C#进阶系列 专题一:深入解析深拷贝和浅拷贝
- C#进阶系列 专题二:你知道Dictionary查找速度为什么快吗?
- C# 开发技巧系列
- C# 开发技巧系列 使用C#操作Word和Excel程序
- C# 开发技巧系列 使用C#操作幻灯片
- C# 开发技巧系列 如何动态设置屏幕分辨率
- C# 开发技巧系列 C#如何实现图片查看器
- C# 开发技巧 如何防止程序多次运行
- C# 开发技巧 实现属于自己的截图工具
- C# 开发技巧 如何使不符合要求的元素等于离它最近的一个元素
- C# 线程处理系列
- C# 线程处理系列 专题一:线程基础
- C# 线程处理系列 专题二:线程池中的工作者线程
- C# 线程处理系列 专题三:线程池中的I/O线程
- C# 线程处理系列 专题四:线程同步
- C# 线程处理系列 专题五:线程同步——事件构造
- C# 线程处理系列 专题六:线程同步——信号量和互斥体
- C# 多线程处理系列专题七——对多线程的补充
- C#网络编程系列
- C# 网络编程系列 专题一:网络协议简介
- C# 网络编程系列 专题二:HTTP协议详解
- C# 网络编程系列 专题三:自定义Web服务器
- C# 网络编程系列 专题四:自定义Web浏览器
- C# 网络编程系列 专题五:TCP编程
- C# 网络编程系列 专题六:UDP编程
- C# 网络编程系列 专题七:UDP编程补充——UDP广播程序的实现
- C# 网络编程系列 专题八:P2P编程
- C# 网络编程系列 专题九:实现类似QQ的即时通信程序
- C# 网络编程系列 专题十:实现简单的邮件收发器
- C# 网络编程系列 专题十一:实现一个基于FTP协议的程序——文件上传下载器
- C# 网络编程系列 专题十二:实现一个简单的FTP服务器
- C# 互操作性入门系列
- C# 互操作性入门系列(一):C#中互操作性介绍
- C# 互操作性入门系列(二):使用平台调用调用Win32 函数
- C# 互操作性入门系列(三):平台调用中的数据封送处理
- C# 互操作性入门系列(四):在C# 中调用COM组件
- CLR
- 谈谈: String 和StringBuilder区别和选择
- 谈谈:程序集加载和反射
- 利用反射获得委托和事件以及创建委托实例和添加事件处理程序
- 谈谈:.Net中的序列化和反序列化
- C#设计模式
- UML类图符号 各种关系说明以及举例
- C#设计模式(1)——单例模式
- C#设计模式(2)——简单工厂模式
- C#设计模式(3)——工厂方法模式
- C#设计模式(4)——抽象工厂模式
- C#设计模式(5)——建造者模式(Builder Pattern)
- C#设计模式(6)——原型模式(Prototype Pattern)
- C#设计模式(7)——适配器模式(Adapter Pattern)
- C#设计模式(8)——桥接模式(Bridge Pattern)
- C#设计模式(9)——装饰者模式(Decorator Pattern)
- C#设计模式(10)——组合模式(Composite Pattern)
- C#设计模式(11)——外观模式(Facade Pattern)
- C#设计模式(12)——享元模式(Flyweight Pattern)
- C#设计模式(13)——代理模式(Proxy Pattern)
- C#设计模式(14)——模板方法模式(Template Method)
- C#设计模式(15)——命令模式(Command Pattern)
- C#设计模式(16)——迭代器模式(Iterator Pattern)
- C#设计模式(17)——观察者模式(Observer Pattern)
- C#设计模式(18)——中介者模式(Mediator Pattern)
- C#设计模式(19)——状态者模式(State Pattern)
- C#设计模式(20)——策略者模式(Stragety Pattern)
- C#设计模式(21)——责任链模式
- C#设计模式(22)——访问者模式(Vistor Pattern)
- C#设计模式(23)——备忘录模式(Memento Pattern)
- C#设计模式总结
- WPF快速入门系列
- WPF快速入门系列(1)——WPF布局概览
- WPF快速入门系列(2)——深入解析依赖属性
- WPF快速入门系列(3)——深入解析WPF事件机制
- WPF快速入门系列(4)——深入解析WPF绑定
- WPF快速入门系列(5)——深入解析WPF命令
- WPF快速入门系列(6)——WPF资源和样式
- WPF快速入门系列(7)——深入解析WPF模板
- WPF快速入门系列(8)——MVVM快速入门
- WPF快速入门系列(9)——WPF任务管理工具实现
- ASP.NET 开发
- ASP.NET 开发必备知识点(1):如何让Asp.net网站运行在自定义的Web服务器上
- ASP.NET 开发必备知识点(2):那些年追过的ASP.NET权限管理
- ASP.NET中实现回调
- 跟我一起学WCF
- 跟我一起学WCF(1)——MSMQ消息队列
- 跟我一起学WCF(2)——利用.NET Remoting技术开发分布式应用
- 跟我一起学WCF(3)——利用Web Services开发分布式应用
- 跟我一起学WCF(3)——利用Web Services开发分布式应用
- 跟我一起学WCF(4)——第一个WCF程序
- 跟我一起学WCF(5)——深入解析服务契约 上篇
- 跟我一起学WCF(6)——深入解析服务契约 下篇
- 跟我一起学WCF(7)——WCF数据契约与序列化详解
- 跟我一起学WCF(8)——WCF中Session、实例管理详解
- 跟我一起学WCF(9)——WCF回调操作的实现
- 跟我一起学WCF(10)——WCF中事务处理
- 跟我一起学WCF(11)——WCF中队列服务详解
- 跟我一起学WCF(12)——WCF中Rest服务入门
- 跟我一起学WCF(13)——WCF系列总结
- .NET领域驱动设计实战系列
- .NET领域驱动设计实战系列 专题一:前期准备之EF CodeFirst
- .NET领域驱动设计实战系列 专题二:结合领域驱动设计的面向服务架构来搭建网上书店
- .NET领域驱动设计实战系列 专题三:前期准备之规约模式(Specification Pattern)
- .NET领域驱动设计实战系列 专题四:前期准备之工作单元模式(Unit Of Work)
- .NET领域驱动设计实战系列 专题五:网上书店规约模式、工作单元模式的引入以及购物车的实现
- .NET领域驱动设计实战系列 专题六:DDD实践案例:网上书店订单功能的实现
- .NET领域驱动设计实战系列 专题七:DDD实践案例:引入事件驱动与中间件机制来实现后台管理功能
- .NET领域驱动设计实战系列 专题八:DDD案例:网上书店分布式消息队列和分布式缓存的实现
- .NET领域驱动设计实战系列 专题九:DDD案例:网上书店AOP和站点地图的实现
- .NET领域驱动设计实战系列 专题十:DDD扩展内容:全面剖析CQRS模式实现
- .NET领域驱动设计实战系列 专题十一:.NET 领域驱动设计实战系列总结