迭代器模式 与 C# IEnumerator/IEnumerable
Part1 迭代器模式 与 接口
IEnumerable
IEnumerator
interface IEnumerable
{
IEnumerator GetEnumerator();
}
// 泛型版本 : IEnumerator<T>
interface IEnumerator
{
object Current { get; }
bool MoveNext();
void Reset();
}
这两个接口用于实现 迭代器 这种设计模式。
迭代器模式:
在软件构建过程中,集合对象内部结构常常变化各异。但对于这些集合对象,我们希望在不暴露其内部结构的同时,可以让外部客户代码透明地访问其中包含的元素;同时这种“透明遍历”也为“同一种算法在多种集合对象上进行操作”提供了可能。使用面向对象技术将这种遍历机制抽象为“迭代器对象”为“应对变化中的集合对象”提供了一种优雅的方式。
迭代器模式是一种行为设计模式,简单而言,就是将对集合的遍历有“外部控制”变为“内部控制”,将其封装起来。
数组就是将遍历完全交由外部处理。
Iterator模式的几个要点
- 迭代抽象:访问一个聚合对象的内容而无需暴露它的内部表示。
- 迭代多态:为遍历不同的集合结构提供一个统一的接口,从而支持同样的算法在不同的集合结构上进行操作。
- 迭代器的健壮性考虑:遍历的同时更改迭代器所在的集合结构,会导致问题。(所以 C# 中在 foreach 操作时,不允许更改集合,如果外部有更改,则会报错)。
Part2 foreach 语句的等价形式(while循环)
foreach(var p in Persons)
{
WriteLine(p);
}
// 等价于一个 while 循环
IEnumerator<Person> enumerator = persons.GetEnumerator();
while(enumerator.MoveNext())
{
Person p = enumerator.Current;
WriteLine(p);
}
- 可以看到,这里并没有调用
Reset方法,此方法通常用于与 COM 的交互操作,许多 .NET 枚举器抛出 NotSupportedException; - 集合可以被 foreach, 不一定需要实现 IEnumerable 接口,有 GetEnumerator 方法即可。
- 一个集合类可以提供多个不同的 GetEnumerator 实现,如 GetEnumerator1,GetEnumerator2,返回不同的 IEnumerator,以实现不同的迭代功能。(见下文)
Part3 IEnumerator 与 yield
一个集合类想要支持被迭代,最主要的是构造一个 Enumerator 类,实现 IEnumerator 接口,在 GetEnumerator 方法中返回这个 Enumerator 类。
如此,在 Enumerator 类中,需要维护 Current 属性和 MoveNext 方法,在 MoveNext 方法中,更新 Current 的值,并返回是否还有后续值的 bool 判断。
在实现 IEnumerator 接口时,通常也要实现其泛型版本 IEnumerator。
这段文字看起来有点晕,实际上,实现一个 IEnumerator 也是一个苦力活。在实际的编程中,一般直接使用已有的集合元素,不必从头实现一个 IEnumerator 。
yield 是 C# 提供语法糖,可以方便的实现 IEnumerator 接口。如:
public IEnumerator<string> GetEnumerator()
{
yield return "A";
yield return "B";
yield return "C";
// ...
yield return "Z";
}
这样,实际上就实现了一个集合,这个集合保存了大写的26个字母。
yield return 语句返回集合的一个元素,并移动到下一个元素,相当于同时维护 Current 和 MoveNext ;yield break 可停止迭代。
使用 yield,编译器会创建一个状态机,用于实际维护 Current 和 MoveNext。
Part4 实现多个不同的 IEnumerator
有一个 MusicCollection 集合类,里面包含了 IList 集合,现在要在其中实现对 Music.Title , Music.Author , Music.Time 进行遍历的支持,可以这么做:
public class MusicCollection
{
private IList<Music> MusicList;
public MusicCollection(IList<Music> musicList)
{
MusicList = musicList;
}
public IEnumerator<string> GetTitleEnumerator()
{
for(int i=0;i<MusicList.Length;i++)
{
yield return MusicList[i].Title;
}
}
public IEnumerator<string> GetAuthorEnumerator()
{
for(int i=0;i<MusicList.Length;i++)
{
yield return MusicList[i].Author;
}
}
public IEnumerator<string> GetTimeEnumerator()
{
for(int i=0;i<MusicList.Length;i++)
{
yield return MusicList[i].Time;
}
}
}
// 外部调用:
pubic class Test
{
public void Test()
{
var musicList = new List<Music>();
var musicCollection = new MusicCollection(musicList);
foreach(string title in musicCollection.GetTitleEnumerator())
{
Console.WriteLine(title);
}
}
}
迭代器中还可以返回迭代器(嵌套),有趣的用法。
Part5 线程安全
迭代显然是非线程安全的,每次IEnumerable都会生成新的IEnumerator,从而形成多个互相不影响的迭代过程。
在迭代过程中,不能修改迭代集合,否则不安全。
参考资料
迭代器模式 与 C# IEnumerator/IEnumerable的更多相关文章
- 设计模式学习之迭代器模式(Iterator,行为型模式)(17)
参考地址:http://www.cnblogs.com/zhili/p/IteratorPattern.html 一.介绍迭代器是针对集合对象而生的,对于集合对象而言,必然涉及到集合元素的添加删除操作 ...
- C#设计模式-迭代器模式
一. 迭代器(Iterator)模式 迭代器是针对集合对象而生的,对于集合对象而言,必然涉及到集合元素的添加删除操作,同时也肯定支持遍历集合元素的操作,我们此时可以把遍历操作也放在集合对象中,但这样的 ...
- 迭代器模式的一种应用场景以及C#对于迭代器的内置支持
迭代器模式 先放上gof中对于迭代器模式的介绍镇楼 意图 提供一种方法顺序访问一个聚合对象中各个元素, 而又不需暴露该对象的内部表示. 别名 游标(Cursor). 动机 一个聚合对象, 如列表(li ...
- C#设计模式(16)——迭代器模式(Iterator Pattern)
一.引言 在上篇博文中分享了我对命令模式的理解,命令模式主要是把行为进行抽象成命令,使得请求者的行为和接受者的行为形成低耦合.在一章中,将介绍一下迭代器模式.下面废话不多说了,直接进入本博文的主题. ...
- C#设计模式——迭代器模式(Iterator Pattern)
一.概述在软件开发过程中,我们可能会希望在不暴露一个集合对象内部结构的同时,可以让外部代码透明地访问其中包含的元素.迭代器模式可以解决这一问题.二.迭代器模式迭代器模式提供一种方法顺序访问一个集合对象 ...
- .NET设计模式(18):迭代器模式(Iterator Pattern)(转)
概述 在面向对象的软件设计中,我们经常会遇到一类集合对象,这类集合对象的内部结构可能有着各种各样的实现,但是归结起来,无非有两点是需要我们去关心的:一是集合内部的数据存储结构,二是遍历集合内部的数据. ...
- [设计模式] Iterator - 迭代器模式:由一份奥利奥早餐联想到的设计模式
Iterator - 迭代器模式 目录 前言 回顾 UML 类图 代码分析 抽象的 UML 类图 思考 前言 这是一包奥利奥(数组),里面藏了很多块奥利奥饼干(数组中的元素),我将它们放在一个碟子上慢 ...
- C#设计模式之十六迭代器模式(Iterator Pattern)【行为型】
一.引言 今天我们开始讲"行为型"设计模式的第三个模式,该模式是[迭代器模式],英文名称是:Iterator Pattern.还是老套路,先从名字上来看看."迭代器模 ...
- [C# 设计模式] Iterator - 迭代器模式:我与一份奥利奥早餐的故事
Iterator - 迭代器模式 目录 前言 回顾 UML 类图 代码分析 抽象的 UML 类图 思考 前言 这是一包奥利奥(数组),里面藏了很多块奥利奥饼干(数组中的元素),我将它们放在一个碟子上慢 ...
随机推荐
- Java并发编程原理与实战三十六:阻塞队列&消息队列
一.阻塞队列 1.阻塞队列BlockingQueue ---->可以理解成生产者消费者的模式---->消费者要等待到生产者生产出来产品.---->而非阻塞队列ConcurrentLi ...
- addclass,removeclass
定义和用法 addClass() 方法向被选元素添加一个或多个类. 该方法不会移除已存在的 class 属性,仅仅添加一个或多个 class 属性. 提示:如需添加多个类,请使用空格分隔类名. rem ...
- python初步学习-python 模块之 sys(持续补充)
sys sys 模块包括了一组非常实用的服务,内含很多函数方法和变量 sys 模块重要函数变量 sys.stdin 标准输出流 sys.stdout 标准输出流 sys.stderr 标准错误流 sy ...
- Hive笔记之数据库操作
创建数据库 hive创建数据库的最简单写法和mysql差不多: create database foo; 仅当名为foo的数据库当前不存在时才创建: create database if not ex ...
- Linux基础-awk、变量、运算符、if
awk 程序的运行就是一些列状态的变量->用变量值的变化去表示 以字母或下划线开头,剩下的部分可以是:字母.数字.下划线. 最好遵循下述规范: 1.以字母开头2.使用中划线或者下划线做单词的连接 ...
- JQuery获取被选中的checkbox的value值
文章源头:http://www.cnblogs.com/td960505/p/6123510.html 以下为使用JQuery获取input checkbox被选中的值代码: <html> ...
- Eureka简介
Eureka是Spring Cloud Netfix 的一个子模块,也是核心模块之一,用于云端服务发现.一个基于RestFul的服务,用于定位服务,以实现云端中间层服务发现和中间层转移. 服务注册与发 ...
- A - ACM Computer Factory(网络流)
题目链接:https://cn.vjudge.net/contest/68128#problem/A 反思:注意拆点,否则的话节点就没用了,还有注意源点和汇点的赋值. AC代码: #include&l ...
- JavaScript验证注册信息
<script language="javascript"> function check_login(form){ if(form.username.value==& ...
- JSON数据填充表格——(三)
1.定义页面请求JSON的按钮与定义一个带表头的表格 请求数据的按钮 <button class="btn btn-primary search_bar_button floatR& ...