C# 枚举器(enumerator)
总结:
1、枚举器就像是序列中的“游标”或“书签”。可以有多个“书签”,移动其中任何一个都可以枚举集合,与其他枚举器互不影响。用来遍历数据结构(单项表链、数组、集合类成员等)。
2、可以使用foreach 遍历枚举器。foreach 用来遍历鸭子类型.点击查看foreach详细用法
什么是枚举器
实现IEnumerator接口的类就是枚举器。
枚举器作用
1、枚举器就像是序列中的“游标”或“书签”。可以有多个“书签”,移动其中任何一个都可以枚举集合,与其他枚举器互不影响。用来遍历数据结构(表链、数组、集合类成员等)。
2、以下案例数组
作为内部数据结构,后期也可以换成数组,链表,树,图等等,而使用者却不用关心这些内部数据表示,这就是迭代器的妙处所在。
存在的问题
1、对于需要递归遍历的数据结构(如二叉树),指示状态可能就会变得相当复杂。为了减少实现此模式所带来的挑战,C# 2.0引入了迭代器, 新增了 yield 上下文关键字。
2、只能顺序遍历
枚举器的原理
指针初始位置不在数据结构内(数组、表链、树等结构)、第一次movenext()后, 数据结构项(表链 、数组等)不为空则返回true,否则为false
IEnumerator<string> enumerator = mc.BlackAndWhite().GetEnumerator();
try
{
//数据结构项(表链 、数组等)不为空则返回true,否则为false while (enumerator.MoveNext())
{
//读取当前指针位置处的值
string shade = enumerator.Current;
Console.Write(shade);
}
}
finally
{
if (enumerator != null)
{
enumerator.Dispose();
}
}
IEnumerator接口
实现了IEnumerator接口的枚举器包含3个public类型的成员:Current、MoveNext()以及Reset()。
在 IEnumerator 嵌套类中实现,以便可以创建多个枚举器。
枚举器内部可以用数组、表链、等其他数据结构。以下案例用数组
Current:返回当前处理的元素。
- 它是只读属性。
- 它返回object类型的引用,所以可以返回任何类型。
MoveNext():把枚举器位置向前到集合中的下一项的方法。
- 它也返回布尔值,指示新的位置是有效位置还是已经超过了序列的尾部。
- 如果新的位置是有效的。
- 如果新的位置是无效的(比如当前位置到达了尾部),方法返回false。
- 枚举器的原始位置在序列中的第一项之前,因此MoveNext必须在第一次使用Current之前调用。
int[] i = {1,1,1,2 };
var ie= i.GetEnumerator();
//错误的写法,原因是枚举器位于集合中第一个元素之前,紧跟在创建枚举器之后。 MoveNext 在读取的值之前,必须调用以将枚举数前移到集合的第一个元素 Current 。
Console.Write(ie.Current);
ie.MoveNext();
Reset():把位置重置为原始状态的方法。(Reset 方法通常会抛出 NotImplementedException,因此不得进行调用。如果需要重新开始枚举,只要新建一个枚举器即可。)
枚举器的实现
这种方式不好,不能创建多个枚举实例。
using System;
using System.Collections;
namespace ConsoleEnum
{
public class cars : IEnumerator,IEnumerable
{
private car[] carlist;
int position = -1;
//Create internal array in constructor.
public cars()
{
carlist= new car[6]
{
new car("Ford",1992),
new car("Fiat",1988),
new car("Buick",1932),
new car("Ford",1932),
new car("Dodge",1999),
new car("Honda",1977)
};
}
//IEnumerator and IEnumerable require these methods.
public IEnumerator GetEnumerator()
{
return (IEnumerator)this;
}
//IEnumerator
public bool MoveNext()
{
position++;
return (position < carlist.Length);
}
//IEnumerable
public void Reset()
{
position = -1;
}
//IEnumerable
public object Current
{
get { return carlist[position];}
}
}
}
本文中的示例尽量简单(所以采用数组而不是其他数据解构(单项表链)),以更好地解释这些接口的使用。不过该案例也反应出一个问题。
如果多线程访问方法就会造成这个实例,由于MoveNext()是共享的。就会导致乱序。
若要使代码更可靠并确保代码使用当前最佳做法准则,请修改代码,如下所示:
最佳做法
- 将 IEnumerable和IEnumerator两个接口的功能分开。集合类本身实现IEnumerable,集合类内部嵌套枚举器 (继承
IEnumerator接口
的类),以便可以创建多个枚举器。 - 枚举器就像是序列中的“游标”或“书签”。可以有多个“书签”,移动其中任何一个都可以枚举集合,与其他枚举器互不影响。
- 为 方法提供
Current
异常处理IEnumerator
。 如果集合的内容更改,将reset
调用 方法。 因此,当前枚举器失效,您将收到IndexOutOfRangeException
异常。 其他情况也可能导致此异常。 因此,实现Try...Catch
块以捕获此异常并引发InvalidOperationException
异常。
using System;
using System.Collections;
namespace ConsoleEnum
{
public class cars : IEnumerable
{
private car[] carlist; //Create internal array in constructor.
public cars()
{
carlist= new car[6]
{
new car("Ford",1992),
new car("Fiat",1988),
new car("Buick",1932),
new car("Ford",1932),
new car("Dodge",1999),
new car("Honda",1977)
};
}
//private enumerator class
private class MyEnumerator:IEnumerator
{
public car[] carlist;
int position = -1; //constructor
public MyEnumerator(car[] list)
{
carlist=list;
}
private IEnumerator getEnumerator()
{
return (IEnumerator)this;
}
//IEnumerator
public bool MoveNext()
{
position++;
return (position < carlist.Length);
}
//IEnumerator
public void Reset()
{
position = -1;
}
//IEnumerator
public object Current
{
get
{
try
{
return carlist[position];
}
catch (IndexOutOfRangeException)
{
throw new InvalidOperationException();
}
}
}
} //end nested class
public IEnumerator GetEnumerator()
{
return new MyEnumerator(carlist);
}
}
}
枚举器在集合中应用
首先、集合必须继承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)的更多相关文章
- ruby迭代器iterator和枚举器Enumerator
编写自定义的迭代器 The defining feature of an iterator method is that it invokes a block of code associatedwi ...
- 关于IEnumerator<T>泛型枚举器 和 IEnumerable<T>
在开发中我们经常会用到 IEnumerable<T> xxx 或者 List<T> xxx 这种集合或者集合接口,实际上就是一个线性表嘛然后结合C#提供的语法糖 foreach ...
- C#知识点-枚举器和迭代器
一.几个基本概念的理解 问题一:为什么数组可以使用foreach输出各元素 答:数组是可枚举类型,它实现了一个枚举器(enumerator)对象:枚举器知道各元素的次序并跟踪它们的位置,然后返回请求的 ...
- C#中的枚举器
更新记录 本文迁移自Panda666原博客,原发布时间:2021年6月28日. 一.先从可枚举类型讲起 1.1 什么是可枚举类型? 可枚举类型,可以简单的理解为: 有一个类,类中有挺多的数据,用一种统 ...
- ruby迭代器枚举器
迭代器一个迭代器是一个方法,这个方法里面有yield语句,使用了yield的方法叫做迭代器,迭代器并非一定要迭代,与传递给这个方法的块进行数据传输 yield将数据传给代码快,代码块再把数据传输给yi ...
- ruby中迭代器枚举器的理解
参考<ruby编程语言>5.3迭代器和可枚举对象 迭代器一个迭代器是一个方法,这个方法里面有yield语句,这个方法里的yield语句,与传递给这个方法的块进行数据传输 yield将数据传 ...
- C#中的枚举器(转)
术语表 Iterator:枚举器(迭代器) 如果你正在创建一个表现和行为都类似于集合的类,允许类的用户使用foreach语句对集合中的成员进行枚举将会是很方便的.这在C# 2.0中比 C# 1.1更容 ...
- C#中的foreach语句与枚举器接口(IEnumerator)及其泛型 相关问题
这个问题从<C#高级编程>数组一节中的foreach语句(6.7.2)发现的. 因为示例代码与之前的章节连贯,所以我修改了一下,把自定义类型改为了int int[] bs = { 2, 3 ...
- C#图解教程 第十八章 枚举器和迭代器
枚举器和迭代器 枚举器和可枚举类型 foreach语句 IEnumerator接口 使用IEnumerable和IEnumerator的示例 泛型枚举接口迭代器 迭代器块使用迭代器来创建枚举器使用迭代 ...
随机推荐
- 前端3D引擎-Cesium自定义动态材质
本文代码基于Vue-cli4和使用WebGL的地图引擎Cesium,主要内容为三维场景下不同对象的动态材质构建. 参考了很多文章,链接附在文末. 为不同的几何对象添加动态材质 不知道这一小节的名称概况 ...
- 字符串工具类ToStringBuilder常用方法介绍
一.简介与引入 1.ToStringBuilder.HashCodeBuilder.EqualsBuilder.ToStringStyle.ReflectionToStringBuilder.Co ...
- selenium - 弹出框死活定位不到
先要确定是不是alert,是才能用,不是的话肯定不能用. 有些弹出框是div层,这种跟平常定位方法一样 有些弹出框是嵌套的iframe层,这种切换iframe就可以了 有些弹出框比较坑,是嵌入的一个窗 ...
- echarts的通用属性的介绍
通常做数据可视化时,会用到统计图,这里我使用的是Echarts,对于第一次用的人来说,还是有点难度的,主要是里面的属性太多,看的头痛,这里我自己做个笔记 这里的配置项手册里面就是查找各种属性了,在Ec ...
- Qt中添加静态库.lb,.a和动态库.dll,.so,头文件和.cpp文件
添加步骤 1.-Qt Creator中,"项目"------"添加库"2.把静态库和动态库文件放到项目文件夹中3.在.pro文件中会添加如下代码: - 添加动态 ...
- centos7 service iptables save 报错
解决办法: 1.systemctl stop firewalld 2.yum install iptables-services 3.systemctl restart iptables 4.ser ...
- JavaCV的摄像头实战之四:抓图
欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 本篇概览 本文是<JavaCV的摄像头实战> ...
- 羽夏看Win系统内核——同步篇
写在前面 此系列是本人一个字一个字码出来的,包括示例和实验截图.由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新. 如有好的建议,欢迎反馈.码字不易, ...
- 「LOJ 6373」NOIP2017 普及组题目大融合
NOIP2017 普及组题目大融合 每个读者需要有某个后缀的书,可以暴力map,复杂度\(o(9*nlog(n))\),也可以反串建trie树,复杂度\(o(9*n)\). 故可以求出需要的最少的RM ...
- SSH 密钥登录
一.什么是SSH? 简单说,SSH是一种网络协议,用于计算机之间的加密登录. 如果一个用户从本地计算机,使用SSH协议登录另一台远程计算机,我们就可以认为,这种登录是安全的,即使被中途截获,密码也不会 ...