引言:

  在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. namespace 迭代器Demo
  4. {
  5. class Program
  6. {
  7. static void Main(string[] args)
  8. {
  9. Friends friendcollection = new Friends();
  10. foreach (Friend f in friendcollection)
  11. {
  12. Console.WriteLine(f.Name);
  13. }
  14. Console.Read();
  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. /// <summary>
  34. ///   朋友集合
  35. /// </summary>
  36. public class Friends : IEnumerable
  37. {
  38. private Friend[] friendarray;
  39. public Friends()
  40. {
  41. friendarray = new Friend[]
  42. {
  43. new Friend("张三"),
  44. new Friend("李四"),
  45. new Friend("王五")
  46. };
  47. }
  48. // 索引器
  49. public Friend this[int index]
  50. {
  51. get { return friendarray[index]; }
  52. }
  53. public int Count
  54. {
  55. get { return friendarray.Length; }
  56. }
  57. // 实现IEnumerable<T>接口方法
  58. public  IEnumerator GetEnumerator()
  59. {
  60. return new FriendIterator(this);
  61. }
  62. }
  63. /// <summary>
  64. ///  自定义迭代器,必须实现 IEnumerator接口
  65. /// </summary>
  66. public class FriendIterator : IEnumerator
  67. {
  68. private readonly Friends friends;
  69. private int index;
  70. private Friend current;
  71. internal FriendIterator(Friends friendcollection)
  72. {
  73. this.friends = friendcollection;
  74. index = 0;
  75. }
  76. #region 实现IEnumerator接口中的方法
  77. public object Current
  78. {
  79. get
  80. {
  81. return this.current;
  82. }
  83. }
  84. public bool MoveNext()
  85. {
  86. if (index + 1 > friends.Count)
  87. {
  88. return false;
  89. }
  90. else
  91. {
  92. this.current = friends[index];
  93. index++;
  94. return true;
  95. }
  96. }
  97. public void Reset()
  98. {
  99. index = 0;
  100. }
  101. #endregion
  102. }
  103. }

运行结果(上面代码中都有详细的注释,这里就不说明了,直接上结果截图):

三、使用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. Console.Read();
  13. }
  14. }
  15. /// <summary>
  16. ///  朋友类
  17. /// </summary>
  18. public class Friend
  19. {
  20. private string name;
  21. public string Name
  22. {
  23. get { return name; }
  24. set { name = value; }
  25. }
  26. public Friend(string name)
  27. {
  28. this.name = name;
  29. }
  30. }
  31. /// <summary>
  32. ///   朋友集合
  33. /// </summary>
  34. public class Friends : IEnumerable
  35. {
  36. private Friend[] friendarray;
  37. public Friends()
  38. {
  39. friendarray = new Friend[]
  40. {
  41. new Friend("张三"),
  42. new Friend("李四"),
  43. new Friend("王五")
  44. };
  45. }
  46. // 索引器
  47. public Friend this[int index]
  48. {
  49. get { return friendarray[index]; }
  50. }
  51. public int Count
  52. {
  53. get { return friendarray.Length; }
  54. }
  55. // C# 2.0中简化迭代器的实现
  56. public IEnumerator GetEnumerator()
  57. {
  58. for (int index = 0; index < friendarray.Length; index++)
  59. {
  60. // 这样就不需要额外定义一个FriendIterator迭代器来实现IEnumerator
  61. // 在C# 2.0中只需要使用下面语句就可以实现一个迭代器
  62. yield return friendarray[index];
  63. }
  64. }
  65. }
  66. }

  在上面代码中有一个yield return 语句,这个语句的作用就是告诉编译器GetEnumerator方法不是一个普通的方法,而是实现一个迭代器的方法,当编译器看到yield return语句时,编译器知道需要实现一个迭代器,所以编译器生成中间代码时为我们生成了一个IEnumerator接口的对象,大家可以通过Reflector工具进行查看,下面是通过Reflector工具得到一张截图:

  从上面截图可以看出,yield return 语句其实是C#中提供的另一个语法糖,简化我们实现迭代器的源代码,把具体实现复杂迭代器的过程交给编译器帮我们去完成,看来C#编译器真是做得非常人性化,把复杂的工作留给自己做,让我们做一个简单的工作就好了。

四、迭代器的执行过程

为了让大家更好的理解迭代器,下面列出迭代器的执行流程:

五、迭代器的延迟计算

  从第四部分中迭代器的执行过程中可以知道迭代器是延迟计算的, 因为迭代的主体在MoveNext()中实现(因为在MoveNext()方法中访问了集合中的当前位置的元素),Foreach中每次遍历执行到in的时候才会调用MoveNext()方法,所以迭代器可以延迟计算,下面通过一个示例来演示迭代器的延迟计算:

  1. namespace 迭代器延迟计算Demo
  2. {
  3. class Program
  4. {
  5. /// <summary>
  6. ///  演示迭代器延迟计算
  7. /// </summary>
  8. /// <param name="args"></param>
  9. static void Main(string[] args)
  10. {
  11. // 测试一
  12. //WithIterator();
  13. //Console.Read();
  14. // 测试二
  15. //WithNoIterator();
  16. //Console.Read();
  17. // 测试三
  18. foreach (int j in WithIterator())
  19. {
  20. Console.WriteLine("在main输出语句中,当前i的值为:{0}", j);
  21. }
  22. Console.Read();
  23. }
  24. public static IEnumerable<int> WithIterator()
  25. {
  26. for (int i = 0; i < 5; i++)
  27. {
  28. Console.WriteLine("在WithIterator方法中的, 当前i的值为:{0}", i);
  29. if (i > 1)
  30. {
  31. yield return i;
  32. }
  33. }
  34. }
  35. public static IEnumerable<int> WithNoIterator()
  36. {
  37. List<int> list = new List<int>();
  38. for (int i = 0; i < 5; i++)
  39. {
  40. Console.WriteLine("当前i的值为:{0}", i);
  41. if (i > 1)
  42. {
  43. list.Add(i);
  44. }
  45. }
  46. return list;
  47. }
  48. }
  49. }

当运行测试一的代码时,控制台中什么都不输出,原因是生成的迭代器延迟了值的输出,大家可以用Reflector工具反编译出编译器生成的中间语言代码就可以发现原因了,下面是一张截图:

从图中可以看出,WithIterator()被编译成下面的代码了(此时编译器把我们自己方法体写的代码给改了):

  1. public static IEnumerable<int> WithIterator()
  2. {
  3. return new <WithIterator>d_0(-2);
  4. }

  从而当我们测试一的代码中调用WithIterator()时,对于编译器而言,就是实例化了一个<WithIterator>d_0的对象(<WithIterator>d_0类是编译看到WithIterator方法中包含Yield return 语句生成的一个迭代器类),所以运行测试一的代码时,控制台中什么都不输出。

当运行测试二的代码时,运行结果就如我们期望的那样输出(这里的运行结果就不解释了,列出来是为了更好说明迭代器的延迟计算):

当我们运行测试三的代码时,运行结果就有点让我们感到疑惑了, 下面先给出运行结果截图,然后在分析原因。

可能刚开始看到上面的结果很多人会有疑问,为什么2,3,4会运行两次的呢?下面具体为大家分析下为什么会有这样的结果。

测试代码三中通过foreach语句来遍历集合时,当运行in的时候就会运行IEnumerator.MoveNext()方法,下面是上面代码的MoveNext()方法的代码截图:

  从截图中可以看到有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 中的特性。

转载于:https://blog.51cto.com/learninghard/1076730

[C#基础知识系列]专题十二:迭代器相关推荐

  1. [C#基础知识系列]专题十:全面解析可空类型

    引言: C# 2.0 中还引入了可空类型,可空类型也是值类型,只是可空类型是包括null的值类型的,下面就介绍下C#2.0中对可空类型的支持具体有哪些内容(最近一直都在思考如何来分享这篇文章的,因为刚 ...

  2. [C# 基础知识系列]专题十四:深入理解Lambda表达式

    引言: 对于刚刚接触Lambda表达式的朋友们,可能会对Lambda表达式感到非常疑惑,它到底是个什么什么样的技术呢?以及它有什么好处和先进的地方呢?下面的介绍将会解除你这些疑惑. 一.Lambda表 ...

  3. [C# 基础知识系列]专题十五:全面解析扩展方法

    引言:  C# 3中所有特性的提出都是更好地为Linq服务的, 充分理解这些基础特性后.对于更深层次地去理解Linq的架构方面会更加简单,从而就可以自己去实现一个简单的ORM框架的,对于Linq的学习 ...

  4. [C#基础知识系列]专题十:全面解析可空类型[转]

    原文链接 主要内容: 1:空合并操作符(?? 操作符) ??操作符也就是"空合并操作符",它代表的意思是两个操作数,如果左边的数不为null时,就返回左边的数,如果左边的数为nul ...

  5. [C#基础知识系列]专题十七:深入理解动态类型

    本专题概要: 动态类型介绍 为什么需要动态类型 动态类型的使用 动态类型背后的故事 动态类型的约束 实现动态行为 总结 引言: 终于迎来了我们C# 4中特性了,C# 4主要有两方面的改善--Com 互 ...

  6. [C# 网络编程系列]专题十二:实现一个简单的FTP服务器

    引言: 休息一个国庆节后好久没有更新文章了,主要是刚开始休息完心态还没有调整过来的, 现在差不多进入状态了, 所以继续和大家分享下网络编程的知识,在本专题中将和大家分享如何自己实现一个简单的FTP服务 ...

  7. [C# 基础知识系列]专题五:当点击按钮时触发Click事件背后发生的事情

    引言: 当我们在点击窗口中的Button控件VS会帮我们自动生成一些代码,我们只需要在Click方法中写一些自己的代码就可以实现触发Click事件后我们Click方法中代码就会执行,然而我一直有一个疑 ...

  8. 【转】[C# 基础知识系列]专题四:事件揭秘

    引言: 前面几个专题对委托进行了详细的介绍的,然后我们在编写代码过程中经常会听到"事件"这个概念的,尤其是写UI的时候,当我们点击一个按钮后VS就会自动帮我们生成一些后台的代码,然 ...

  9. [C# 基础知识系列]专题六:泛型基础篇——为什么引入泛型

    引言: 前面专题主要介绍了C#1中的2个核心特性--委托和事件,然而在C# 2.0中又引入一个很重要的特性,它就是泛型,大家在平常的操作中肯定会经常碰到并使用它,如果你对于它的一些相关特性还不是很了解 ...

最新文章

  1. matlab中help所有函数功能的英文翻译
  2. 上传文件 苹果系统选不了excel_每日一课 | 几个好用的Excel技巧,安利了(五)...
  3. 【Android 应用开发】Canvas 精准绘制文字 ( 测量文本真实边界 | 将文本中心点与给定中心点对齐 )
  4. nylgACM_105_九的余数
  5. nx600打印机打印设置_win7打印机共享怎么设置
  6. python 批量修改密码
  7. Chrome , Firfox 对应IE fireEvent 的方法
  8. java jdbc 参数 转义_jdbc URL中的各个参数详解
  9. SMT Kingdom v8.5 地震解释软件\
  10. 日程表模板html,excel日程表模板(每日工作时间表模板)
  11. oracle查询某个时间段的数据
  12. Python合并pdf文件
  13. confusion matrix的理解
  14. 2022 CCF中国软件大会(CCF ChinaSoft)“AI软件系统工程化技术与规范”论坛成功召开...
  15. debian linux手机安装,在 Android 系统上安装 Debian Linux 与 R
  16. 高通Q+A平台 android gcore解析环境搭建
  17. 出来行,迟早是要还的(篇六):衣带渐宽终不悔
  18. 精英计算机主板,精英主板
  19. C++实现DES加密解密算法
  20. BeJSON—实用网站(二)

热门文章

  1. I need to follow my heart.
  2. linux 文件的打包和解压
  3. 51nod 1368:黑白棋 二分图最大匹配
  4. 程序员的奋斗史(八)——懒人造就方法
  5. Windows Phone 7 不温不火学习之《项目模板》
  6. vue脚手架项目技术集合
  7. 一步步揭开 原型链的面纱 面试再也不慌 原型链
  8. 文本超出显示省略号/数字英文字母折行有关css 属性/显示两行,第二行省略号显示css方法...
  9. rman命令学习-tina(下)
  10. C#Windows服务程序安装常见问题解决方法