总结:

1、枚举器就像是序列中的“游标”或“书签”。可以有多个“书签”,移动其中任何一个都可以枚举集合,与其他枚举器互不影响。用来遍历数据结构(单项表链、数组、集合类成员等)。

2、可以使用foreach 遍历枚举器。foreach 用来遍历鸭子类型.点击查看foreach详细用法

什么是枚举器

实现IEnumerator接口的类就是枚举器。

枚举器作用

1、枚举器就像是序列中的“游标”或“书签”。可以有多个“书签”,移动其中任何一个都可以枚举集合,与其他枚举器互不影响。用来遍历数据结构(表链、数组、集合类成员等)。

2、以下案例数组 作为内部数据结构,后期也可以换成数组,链表,树,图等等,而使用者却不用关心这些内部数据表示,这就是迭代器的妙处所在。

存在的问题

1、对于需要递归遍历的数据结构(如二叉树),指示状态可能就会变得相当复杂。为了减少实现此模式所带来的挑战,C# 2.0引入了迭代器, 新增了 yield 上下文关键字。
2、只能顺序遍历

枚举器的原理

指针初始位置不在数据结构内(数组、表链、树等结构)、第一次movenext()后, 数据结构项(表链 、数组等)不为空则返回true,否则为false

  1. IEnumerator<string> enumerator = mc.BlackAndWhite().GetEnumerator();
  2. try
  3. {
  4. //数据结构项(表链 、数组等)不为空则返回true,否则为false
  5.  
  6. while (enumerator.MoveNext())
  7. {
  8. //读取当前指针位置处的值
  9. string shade = enumerator.Current;
  10. Console.Write(shade);
  11. }
  12. }
  13. finally
  14. {
  15. if (enumerator != null)
  16. {
  17. enumerator.Dispose();
  18. }
  19. }

IEnumerator接口

实现了IEnumerator接口的枚举器包含3个public类型的成员Current、MoveNext()以及Reset()

在 IEnumerator 嵌套类中实现,以便可以创建多个枚举器。

枚举器内部可以用数组、表链、等其他数据结构。以下案例用数组

Current:返回当前处理的元素。

  • 它是只读属性。
  • 它返回object类型的引用,所以可以返回任何类型

MoveNext():把枚举器位置向前到集合中的下一项方法

  • 它也返回布尔值,指示新的位置是有效位置还是已经超过了序列的尾部。
  • 如果新的位置是有效的。
  • 如果新的位置是无效的(比如当前位置到达了尾部),方法返回false。
  • 枚举器的原始位置在序列中的第一项之前,因此MoveNext必须在第一次使用Current之前调用。
    1. int[] i = {1,1,1,2 };
    2. var ie= i.GetEnumerator();
    3. //错误的写法,原因是枚举器位于集合中第一个元素之前,紧跟在创建枚举器之后。 MoveNext 在读取的值之前,必须调用以将枚举数前移到集合的第一个元素 Current 。
    4. Console.Write(ie.Current);
    5. ie.MoveNext();

Reset():把位置重置为原始状态方法(Reset 方法通常会抛出 NotImplementedException,因此不得进行调用。如果需要重新开始枚举,只要新建一个枚举器即可。)

枚举器的实现

这种方式不好,不能创建多个枚举实例。

  1. using System;
  2. using System.Collections;
  3. namespace ConsoleEnum
  4. {
  5. public class cars : IEnumerator,IEnumerable
  6. {
  7. private car[] carlist;
  8. int position = -1;
  9. //Create internal array in constructor.
  10. public cars()
  11. {
  12. carlist= new car[6]
  13. {
  14. new car("Ford",1992),
  15. new car("Fiat",1988),
  16. new car("Buick",1932),
  17. new car("Ford",1932),
  18. new car("Dodge",1999),
  19. new car("Honda",1977)
  20. };
  21. }
  22. //IEnumerator and IEnumerable require these methods.
  23. public IEnumerator GetEnumerator()
  24. {
  25. return (IEnumerator)this;
  26. }
  27. //IEnumerator
  28. public bool MoveNext()
  29. {
  30. position++;
  31. return (position < carlist.Length);
  32. }
  33. //IEnumerable
  34. public void Reset()
  35. {
  36. position = -1;
  37. }
  38. //IEnumerable
  39. public object Current
  40. {
  41. get { return carlist[position];}
  42. }
  43. }
  44. }

本文中的示例尽量简单(所以采用数组而不是其他数据解构(单项表链)),以更好地解释这些接口的使用。不过该案例也反应出一个问题。

如果多线程访问方法就会造成这个实例,由于MoveNext()是共享的。就会导致乱序。

若要使代码更可靠并确保代码使用当前最佳做法准则,请修改代码,如下所示:

最佳做法

  • 将 IEnumerable和IEnumerator两个接口的功能分开。集合类本身实现IEnumerable,集合类内部嵌套枚举器 (继承IEnumerator接口的类),以便可以创建多个枚举器。
  • 枚举器就像是序列中的“游标”或“书签”。可以有多个“书签”,移动其中任何一个都可以枚举集合,与其他枚举器互不影响。
  • 为 方法提供 Current 异常处理 IEnumerator 。 如果集合的内容更改,将 reset 调用 方法。 因此,当前枚举器失效,您将收到 IndexOutOfRangeException 异常。 其他情况也可能导致此异常。 因此,实现 Try...Catch 块以捕获此异常并引发 InvalidOperationException 异常。
  1. using System;
  2. using System.Collections;
  3. namespace ConsoleEnum
  4. {
  5. public class cars : IEnumerable
  6. {
  7. private car[] carlist;
  8.  
  9. //Create internal array in constructor.
  10. public cars()
  11. {
  12. carlist= new car[6]
  13. {
  14. new car("Ford",1992),
  15. new car("Fiat",1988),
  16. new car("Buick",1932),
  17. new car("Ford",1932),
  18. new car("Dodge",1999),
  19. new car("Honda",1977)
  20. };
  21. }
  22. //private enumerator class
  23. private class MyEnumerator:IEnumerator
  24. {
  25. public car[] carlist;
  26. int position = -1;
  27.  
  28. //constructor
  29. public MyEnumerator(car[] list)
  30. {
  31. carlist=list;
  32. }
  33. private IEnumerator getEnumerator()
  34. {
  35. return (IEnumerator)this;
  36. }
  37. //IEnumerator
  38. public bool MoveNext()
  39. {
  40. position++;
  41. return (position < carlist.Length);
  42. }
  43. //IEnumerator
  44. public void Reset()
  45. {
  46. position = -1;
  47. }
  48. //IEnumerator
  49. public object Current
  50. {
  51. get
  52. {
  53. try
  54. {
  55. return carlist[position];
  56. }
  57. catch (IndexOutOfRangeException)
  58. {
  59. throw new InvalidOperationException();
  60. }
  61. }
  62. }
  63. } //end nested class
  64. public IEnumerator GetEnumerator()
  65. {
  66. return new MyEnumerator(carlist);
  67. }
  68. }
  69. }

枚举器在集合中应用

首先、集合必须继承IEnumerable 接口,该接口就是告诉别人他是可以枚举的,他的内部已经实现了枚举器。别人可以通过IEnumerable 接口 提供的GetEnumerator()方法获得枚举器。然后通过枚举器的movenext访问集合成员。

第二、然后需要在集合类的内部放一个枚举器(嵌套一个现实 IEnumerator接口 的类)。然把集合自身的单向链表传入枚举器,枚举器就像放在链表上的游标。

第三、别人就可以通过获取调用集合类的GetEnumerator()方法,获取到集合类的枚举器。通过枚举器顺序的访问集合。

实现IEnumerable接口的类有哪些:

数组、集合类 等等

泛型枚举接口

目前我们描述的枚举接口都是非泛型版本。实际上,在大多数情况下你应该使用泛型版本IEnumerable<T>IEnumerator<T>。它们叫做泛型是因为使用了C#泛型(参见第17章),其使用方法和非泛型形式差不多。

两者间的本质差别如下:

  • 对于非泛型接口形式:

    • IEnumerable接口的GetEnumerator方法返回实现IEnumerator枚举器类的实例
    • 实现IEnumerator的类实现了Current属性,它返回object的引用,然后我们必须把它转化为实际类型的对象
  • 对于泛型接口形式:
    • IEnumerable<T>接口的GetEnumerator方法返回实现IEnumator<T>的枚举器类的实例
    • 实现IEnumerator<T>的类实现了Current属性,它返回实际类型的对象,而不是object基类的引用

需要重点注意的是,我们目前所看到的非泛型接口的实现不是类型安全的。它们返回object类型的引用,然后必须转化为实际类型。

泛型接口的枚举器是类型安全的,它返回实际类型的引用。如果要创建自己的可枚举类,应该实现这些泛型接口。非泛型版本可用于C#2.0以前没有泛型的遗留代码。

尽管泛型版本和非泛型版本一样简单易用,但其结构略显复杂。

C# 枚举器(enumerator)的更多相关文章

  1. ruby迭代器iterator和枚举器Enumerator

    编写自定义的迭代器 The defining feature of an iterator method is that it invokes a block of code associatedwi ...

  2. 关于IEnumerator<T>泛型枚举器 和 IEnumerable<T>

    在开发中我们经常会用到 IEnumerable<T> xxx 或者 List<T> xxx 这种集合或者集合接口,实际上就是一个线性表嘛然后结合C#提供的语法糖 foreach ...

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

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

  4. C#中的枚举器

    更新记录 本文迁移自Panda666原博客,原发布时间:2021年6月28日. 一.先从可枚举类型讲起 1.1 什么是可枚举类型? 可枚举类型,可以简单的理解为: 有一个类,类中有挺多的数据,用一种统 ...

  5. ruby迭代器枚举器

    迭代器一个迭代器是一个方法,这个方法里面有yield语句,使用了yield的方法叫做迭代器,迭代器并非一定要迭代,与传递给这个方法的块进行数据传输 yield将数据传给代码快,代码块再把数据传输给yi ...

  6. ruby中迭代器枚举器的理解

    参考<ruby编程语言>5.3迭代器和可枚举对象 迭代器一个迭代器是一个方法,这个方法里面有yield语句,这个方法里的yield语句,与传递给这个方法的块进行数据传输 yield将数据传 ...

  7. C#中的枚举器(转)

    术语表 Iterator:枚举器(迭代器) 如果你正在创建一个表现和行为都类似于集合的类,允许类的用户使用foreach语句对集合中的成员进行枚举将会是很方便的.这在C# 2.0中比 C# 1.1更容 ...

  8. C#中的foreach语句与枚举器接口(IEnumerator)及其泛型 相关问题

    这个问题从<C#高级编程>数组一节中的foreach语句(6.7.2)发现的. 因为示例代码与之前的章节连贯,所以我修改了一下,把自定义类型改为了int int[] bs = { 2, 3 ...

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

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

随机推荐

  1. DEEP LEARNING WITH PYTORCH: A 60 MINUTE BLITZ | TRAINING A CLASSIFIER

    你已经知道怎样定义神经网络,计算损失和更新网络权重.现在你可能会想, 那么,数据呢? 通常,当你需要解决有关图像.文本或音频数据的问题,你可以使用python标准库加载数据并转换为numpy arra ...

  2. MyCms 开源自媒体系统,系统配置字段说明

    功能说明 站点名称:站点的简要描述名称,没有启用SEO插件时,将用此字段用作站点标题. 站点地址:站点的完整地址(包含 http/https),此字段有多处依赖用于生成链接,请正确填写. 站点logo ...

  3. python全局解释器GIL锁(-死锁)

    目录 一:Python中的GIL锁 1.GIL介绍 2.GIL的作用 3.cpython 4.内存管理>>>垃圾回收机制 二:全局解释器锁GIL 1.GIL特点 三:计算密集型与IO ...

  4. Spring专题2: DI,IOC 控制反转和依赖注入

    合集目录 Spring专题2: DI,IOC 控制反转和依赖注入 https://docs.spring.io/spring/docs/2.5.x/reference/aop.html https:/ ...

  5. java中的数组 和 封装

    package com.aaa.zxf; /** * 一. 普通java项目 导入测试类的方法. * ideal 中 java 项目中导入测试类的方法 手动写入 * import org.junit. ...

  6. Visual Studio 2022 下载链接及激活密钥

    Visual Studio 2022 下载链接:https://visualstudio.microsoft.com/zh-hans/vs/ 激活码: 专业版: TD244-P4NB7-YQ6XK-Y ...

  7. AT5801 [AGC043D] Merge Triplets

    这种排列生成排列的题目我们一般可以考虑生成排列合法的充要条件. 首先可以发现的一点就是该生成排列的任意一个数 \(p_i\) 一定不存在连续的三个数 \(p_{i + 1}, p_{i + 2}, p ...

  8. super、this、抽象类、继承

    super 代表是父类对象,并且在创建子类对象时,就先创建父类对象,再创建子类对象,可以在子类方法中使用super调用父类的非私有方法. this 代表本类对象,谁创建就代表谁 实体类,被创建的cla ...

  9. js-小知识点

    强类型定义语言和弱类型定义语言1)强类型定义语言: 强制数据类型定义的语言.也就是说,一旦一个变量被指定了某个数据类型,如果不经过强制转换,那么它就永远是这个数据类型了.举个例子:如果你定义了一个整型 ...

  10. 代理模式应用-AOP事务(转)

    https://jinnianshilongnian.iteye.com/blog/14872351.预备知识 aop概念请参考[http://www.iteye.com/topic/1122401] ...