大道至简,始终认为简洁是一门优秀的编程语言的一个必要条件。相对来说,C#是比较简洁的,也越来越简洁。在C#中,一个关键字或者语法糖在编译器层面为我们做了很多乏味的工作,可能实现的是一个设计模式,甚至是一个算法。例如:lock关键字让用对象获取互斥锁从而实现线程同步,本质上是通过Monitor类来实现的,显然简洁很多。本文要讲的枚举数和迭代器在.net集合类被广泛使用,当然遵循着简洁的设计思想。

1.枚举数

1.1foreach的本质

我们知道,实现了IEnumerable接口的类型对象是可foreach遍历的,那么本质是什么呢?原来,在IEnumerable接口中定义这样一个方法:IEnumerator GetEnumerator(),

IEnumerator接口定义如下:

  1. public interface IEnumerator
  2. {
  3. // 摘要:
  4. // 获取集合中的当前元素。
  5. //
  6. // 返回结果:
  7. // 集合中的当前元素。
  8. //
  9. // 异常:
  10. // System.InvalidOperationException:
  11. // 枚举数定位在该集合的第一个元素之前或最后一个元素之后。
  12. object Current { get; }
  13.  
  14. // 摘要:
  15. // 将枚举数推进到集合的下一个元素。
  16. //
  17. // 返回结果:
  18. // 如果枚举数成功地推进到下一个元素,则为 true;如果枚举数越过集合的结尾,则为 false。
  19. //
  20. // 异常:
  21. // System.InvalidOperationException:
  22. // 在创建了枚举数后集合被修改了。
  23. bool MoveNext();
  24. //
  25. // 摘要:
  26. // 将枚举数设置为其初始位置,该位置位于集合中第一个元素之前。
  27. //
  28. // 异常:
  29. // System.InvalidOperationException:
  30. // 在创建了枚举数后集合被修改了。
  31. void Reset();
  32. }

通过GetEnumerator方法,IEnumerable接口类型对象可以按需获取一个枚举数对象,枚举数可依次返回请求的集合元素作为迭代变量。

1.2枚举数的几种形式

上面说到实现了IEnumerable接口的类型对象是可foreach遍历的,这是充分不必要条件,实际上实现了IEnumerator GetEnumerator()方法的类型对象都是可枚举的。这里一共有三种形式:

  • IEnumerator/IEnumerable非泛型接口形式
  • IEnumerator<T>/IEnumerable<T>泛型接口形式
  • 自实现形式

1.2.1IEnumerator/IEnumerable非泛型接口形式

我们来简单实现一下:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Collections;
  6.  
  7. namespace ConsoleApplication1
  8. {
  9. class Program
  10. {
  11. static void Main(string[] args)
  12. {
  13. EnumerableEx arr = new object[] { "jello", , 'M' };
  14. foreach (var item in arr)
  15. {
  16. Console.WriteLine(item);
  17. }
  18. Console.ReadKey();
  19. }
  20. }
  21.  
  22. class EnumerableEx : IEnumerable
  23. {
  24. object[] arr;
  25.  
  26. public static implicit operator EnumerableEx(object[] _arr)
  27. {
  28. EnumerableEx _enum = new EnumerableEx();
  29. _enum.arr = new object[_arr.Length];
  30. for (int i = ; i < _arr.Length; i++)
  31. {
  32. _enum.arr[i] = _arr[i];
  33. }
  34. return _enum;
  35. }
  36.  
  37. public IEnumerator GetEnumerator()
  38. {
  39. return new EnumeratorEx(arr);
  40. }
  41. }
  42.  
  43. class EnumeratorEx : IEnumerator
  44. {
  45. private int _pos = -;//当前元素位置
  46. private object[] _array;//要遍历的数组
  47.  
  48. //构造函数
  49. public EnumeratorEx(object[] array)
  50. {
  51. _array = array;
  52. }
  53. //迭代变量
  54. public object Current
  55. {
  56. get
  57. {
  58. if (_pos == - || _pos >= _array.Length)
  59. throw new InvalidOperationException();
  60. return _array[_pos];
  61. }
  62. }
  63. //移位
  64. public bool MoveNext()
  65. {
  66. if (_pos < _array.Length - )
  67. {
  68. _pos++;
  69. return true;
  70. }
  71. else
  72. return false;
  73. }
  74. //重置
  75. public void Reset()
  76. {
  77. _pos = -;
  78. }
  79. }
  80.  
  81. }

这里首先向IEnumerable提供了需遍历的数组(使用了隐式用户自定义转换),在foreach中首先会调用GetEnumerator方法,然后MoveNext移到下一个位置,Current即为迭代变量。

1.2.2IEnumerator<T>/IEnumerable<T>泛型接口形式

非泛型接口形式中迭代变量是Object类型(非类型安全),这无法避免装箱和拆箱,尤其是当元素个数很多的时候,性能会消耗很大,因此引入了泛型接口形式。

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5.  
  6. namespace ConsoleApplication1
  7. {
  8. class Program1
  9. {
  10. public static void Main(string[] args)
  11. {
  12. EnumerableEx<int> arr = new int[] { , , };
  13. foreach (var item in arr)
  14. {
  15. Console.WriteLine(item);
  16. }
  17. Console.ReadKey();
  18. }
  19. }
  20.  
  21. class EnumerableEx<T> : IEnumerable<T>
  22. {
  23. T[] arr;
  24.  
  25. public static implicit operator EnumerableEx<T>(T[] _arr)
  26. {
  27. EnumerableEx<T> _enum = new EnumerableEx<T>();
  28. _enum.arr = new T[_arr.Length];
  29. for (int i = ; i < _arr.Length; i++)
  30. {
  31. _enum.arr[i] = _arr[i];
  32. }
  33. return _enum;
  34. }
  35.  
  36. public IEnumerator<T> GetEnumerator()
  37. {
  38. return new EnumeratorEx<T>(arr);
  39. }
  40.  
  41. System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
  42. {
  43. return new EnumeratorEx<T>(arr);
  44. }
  45. }
  46.  
  47. class EnumeratorEx<T> : IEnumerator<T>
  48. {
  49. private int _pos = -;//当前元素位置
  50. private T[] _array;//要遍历的数组
  51.  
  52. public EnumeratorEx(T[] array)
  53. {
  54. _array = array;
  55. }
  56.  
  57. public T Current
  58. {
  59. get
  60. {
  61. if (_pos == - || _pos >= _array.Length)
  62. throw new InvalidOperationException();
  63. return _array[_pos];
  64. }
  65. }
  66.  
  67. public void Dispose()
  68. {
  69. //可用于释放非托管资源
  70. }
  71.  
  72. object System.Collections.IEnumerator.Current
  73. {
  74. get
  75. {
  76. if (_pos == - || _pos >= _array.Length)
  77. throw new InvalidOperationException();
  78. return _array[_pos];
  79. }
  80. }
  81.  
  82. public bool MoveNext()
  83. {
  84. if (_pos < _array.Length - )
  85. {
  86. _pos++;
  87. return true;
  88. }
  89. else
  90. return false;
  91. }
  92.  
  93. public void Reset()
  94. {
  95. _pos = -;
  96. }
  97. }
  98.  
  99. }

和非泛型接口形式基本一样,IEnumerable<T>除了继承IEnumerable接口,还继承了IDisposable接口用来释放非托管资源。

1.2.3自实现形式

自实现形式不继承自上面的接口,自定义一个实现GetEnumerator()的类和一个实现Current和MoveNext的类,好处是更加灵活,缺点是通用性差。

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Collections;
  6.  
  7. namespace ConsoleApplication1
  8. {
  9. class Program2
  10. {
  11. public static void Main(string[] args)
  12. {
  13. MyEnumerable<int> arr = new int[] { , , };
  14. foreach (var item in arr)
  15. {
  16. Console.WriteLine(item);
  17. }
  18. Console.ReadKey();
  19. }
  20. }
  21. class MyEnumerable<T>
  22. {
  23. T[] arr;
  24.  
  25. public static implicit operator MyEnumerable<T>(T[] _arr)
  26. {
  27. MyEnumerable<T> _enum = new MyEnumerable<T>();
  28. _enum.arr = new T[_arr.Length];
  29. for (int i = ; i < _arr.Length; i++)
  30. {
  31. _enum.arr[i] = _arr[i];
  32. }
  33. return _enum;
  34. }
  35.  
  36. public MyEnumerator<T> GetEnumerator()
  37. {
  38. return new MyEnumerator<T>(arr);
  39. }
  40. }
  41. class MyEnumerator<T>
  42. {
  43. private int _pos = -;//当前元素位置
  44. private T[] _array;//要遍历的数组
  45.  
  46. public MyEnumerator(T[] array)
  47. {
  48. _array = array;
  49. }
  50.  
  51. public T Current
  52. {
  53. get
  54. {
  55. if (_pos == - || _pos >= _array.Length)
  56. throw new InvalidOperationException();
  57. return _array[_pos];
  58. }
  59. }
  60.  
  61. public bool MoveNext()
  62. {
  63. if (_pos < _array.Length - )
  64. {
  65. _pos++;
  66. return true;
  67. }
  68. else
  69. return false;
  70. }
  71.  
  72. public void Reset()
  73. {
  74. _pos = -;
  75. }
  76. }
  77. }

需要注意的是:Reset方法并不是必须要实现的。

2.迭代器

2.1What's 迭代器

迭代器是在.net2.0中引入的一种结构,旨在更加简单地创建枚举数和可枚举类型。先来看一个简单例子:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5.  
  6. namespace ConsoleApplication1
  7. {
  8. class Program3
  9. {
  10. public static void Main(string[] args)
  11. {
  12. Iteration iteration = new Iteration();
  13. foreach (var item in iteration)
  14. {
  15. Console.WriteLine(item);
  16. }
  17. Console.ReadKey();
  18. }
  19.  
  20. }
  21. class Iteration
  22. {
  23. public IEnumerator<int> GetNums()
  24. {
  25. for (int i = ; i < ; i++)
  26. {
  27. yield return i;
  28. }
  29. }
  30.  
  31. public IEnumerator<int> GetEnumerator()
  32. {
  33. return GetNums();
  34. }
  35. }
  36. }

上面是通过yield return来获取枚举数的,通过运行结果发现,循环体内的yield return并没有在第一次迭代中返回,而是每次访问迭代变量时都能获取一个新元素值。

由一个或多个yield语句组成的代码块称为迭代器块,它和普通的代码块不同,并不是依次执行的,仅当需要获取迭代变量值时执行一次。

上面举了一个使用迭代器来创建枚举数的例子,其实,使用迭代器还可以创建可枚举类型:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5.  
  6. namespace ConsoleApplication1
  7. {
  8. class Program3
  9. {
  10. public static void Main(string[] args)
  11. {
  12. Iteration iteration = new Iteration();
  13. foreach (var item in iteration)
  14. {
  15. Console.WriteLine(item);
  16. }
  17. Console.ReadKey();
  18. }
  19.  
  20. }
  21. class Iteration
  22. {
  23. public IEnumerable<int> GetNums()
  24. {
  25. for (int i = ; i < ; i++)
  26. {
  27. yield return i;
  28. }
  29. }
  30.  
  31. public IEnumerator<int> GetEnumerator()
  32. {
  33. return GetNums().GetEnumerator();
  34. }
  35. }
  36. }

上面的两段代码有两个地方的不同:一是GetNums方法返回类型不同;二是GetEnumerator方法实现的不同。

2.2迭代器本质

我们使用简单的yield return就可以创建枚举数或可枚举类型,那么在编译器层面究竟做了些什么呢?通过IL代码可以管中窥豹:

原来,编译器在遇到迭代器块时会生成一个嵌套类,这个类实现了IEnumerable<T>和IEnumerator<T>等接口,在这个类中维护了一个拥有四个状态的状态机:

  1. Before:首次调用MoveNext的初始状态
  2. Running:调用MoveNext后进入该状态。在这个状态中,枚举数检测并设置下一项的位置。在遇到yield return、yield break或迭代器块结束时退出状态
  3. Suspended:状态机等待下次调用MoveNext的状态
  4. After:没有更多项可以枚举

如果状态机在Before或Suspended状态有一次MoveNext调用就进入Running状态。在Running状态中检测集合的下一项并设置位置。如果有更多项状态机会转入Suspended状态,如果没有更多项则转入并保持在After状态,如图所示:

3.总结

总而言之,其实是对迭代器设计模式的运用简化。既不暴露集合的内部结构,又可让外部代码透明的访问集合内部的数据,这是迭代器设计模式的思想。

C#枚举数和迭代器的更多相关文章

  1. C#-14 枚举器和迭代器

    一 枚举器和可枚举类型 当我们为数组使用foreach语句时,这个语句为我们依次取出了数组中的每一个元素. var arrInt = new int[] { 11, 12, 13, 14 }; for ...

  2. C#的枚举数(Enumerator)和可枚举类型(Enumerable)

    数组可以被foreach语句遍历数组中的元素,原因是数组可以按需提供一个叫做枚举数(enumerator)的对象.枚举数可以依次返回请求的数组的元素. 对于有枚举数的类型而言,必须有一个方法来获取它们 ...

  3. C#图解教程 第十八章 枚举器和迭代器

    枚举器和迭代器 枚举器和可枚举类型 foreach语句 IEnumerator接口 使用IEnumerable和IEnumerator的示例 泛型枚举接口迭代器 迭代器块使用迭代器来创建枚举器使用迭代 ...

  4. C# 枚举器和迭代器

    一.枚举器(enumerator)和可枚举类型(enumeration) 我们都知道foreach语句可以用来遍历数组中的元素,但你有没有想过为什么它可以被foreach处理呢? 这是因为数组可以按需 ...

  5. 【C#】IEnumrator的枚举数和IEnumerable接口

    声明IEnumerator的枚举数 要创建非泛型接口的枚举数,必须声明实现IEnumerator接口的类,IEnumerator接口有如下特性: 1.她是System.Collections命名空间的 ...

  6. 设计模式 - 适配器模式(adapter pattern) 枚举器和迭代器 具体解释

    适配器模式(adapter pattern) 枚举器和迭代器 具体解释 本文地址: http://blog.csdn.net/caroline_wendy 參考适配器模式(adapter patter ...

  7. 实现自定义集合的可枚举类型(IEnumerable)和枚举数(IEnumerator )

    下面的代码示例演示如何实现自定义集合的 IEnumerable 和 IEnumerator 接口: using System; using System.Collections; using Syst ...

  8. 暴力枚举-数长方形(hdu5258)

    数长方形 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submis ...

  9. C#知识点-枚举器和迭代器

    一.几个基本概念的理解 问题一:为什么数组可以使用foreach输出各元素 答:数组是可枚举类型,它实现了一个枚举器(enumerator)对象:枚举器知道各元素的次序并跟踪它们的位置,然后返回请求的 ...

随机推荐

  1. Java垃圾回收机制以及内存泄露

    1.Java的内存泄露介绍 首先明白一下内存泄露的概念:内存泄露是指程序执行过程动态分配了内存,可是在程序结束的时候这块内存没有被释放,从而导致这块内存不可用,这就是内存 泄露,重新启动计算机能够解决 ...

  2. eclipse 安装vrapper vim插件

    http://vrapper.sourceforge.net/update-site/stable 如果安装不上,设置下代理./window/pereference/network* - manul

  3. HDU 1164 Eddy&#39;s research I【素数筛选法】

    思路:将输入的这个数分成n个素数的相乘的结果,用一个数组存储起来.之后再输出就能够了 Eddy's research I Time Limit: 2000/1000 MS (Java/Others)  ...

  4. hdu4635(最多加多少边,使得有向图不是强连通图)

    连边的最后肯定是两个集合x,yx集合的每个元素,到y集合中的每个元素都是单向的边x集合,和y集合都是完全图设a为x集合的点的个数, b为y集合的那么答案就是 a * b + a*(a-1) + b*( ...

  5. hdu1513 (滚动数据压缩空间)

    给定一个字符串,问最少添加多少个字符可以使得这个字符串变成回文串 if(str[i]==str[j]) dp[i][j] = dp[i+1][j-1] else dp[i][j] = min(dp[i ...

  6. Android App 内存泄漏Handler

    Android App 内存泄露之Handler Handler也是造成内存泄露的一个重要的源头,主要Handler属于TLS(Thread Local Storage)变量,生命周期和Activit ...

  7. 数据市中心全省中国mysql脚本

    1.查尔斯省 watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2h6aGFvY2hhbw==/font/5a6L5L2T/fontsize/400/fill ...

  8. Swing多线程编程(转)

    关键字: Swing,多线程,GUI,SwingWorker 摘要: 本文论述了怎样开发多线程的Swing程序,从而提高Swing程序的响应速度和性能.     近期,我将推出一系列研究Swing程序 ...

  9. C#实现远程机器管理

    原文:C#实现远程机器管理 目前处于待离职状态,原先所有的工作都在进行交接,过程当中不乏有很多先前整理的和动手尝试实现的功能:我的主页中已经列出来一部分内容,有兴趣的可以前往看一看. 接下来的内容主要 ...

  10. 下的生产环境was重新启动不同意,怎么做?

    前一段时间上线.遇到一个jndi问题,它是如何是个问题?它是在测试环境中的原始没有问题,在生产环境中,您无法连接生产数据库,然后发现问题,那是,ibm工具生成在测试环境中自己主动的连接jndi资源文件 ...