迭代器模式 与 C# IEnumerator/IEnumerable

摘要:
(见下文)Part3Enumerator和yield是希望支持迭代的集合类。主要是构造Enumerator类,实现IEnumerator接口,并在GetEnumerator方法中返回Enumerator。这样,在Enumerator类中,需要维护Current属性和MoveNext方法。在MoveNext方法中,您需要更新Current的值,并返回是否有后续值的布尔判断。在实现IEnumerator接口时,通常需要实现其通用版本IEnumerater。Yield是C#提供的语法糖,它可以很容易地实现IEnumerator接口。第5部分线程安全迭代显然是非线程安全的。每个IEnumerable将生成一个新的IEnumerator,从而形成多个互不影响的迭代过程。

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);
}

  1. 可以看到,这里并没有调用 Reset 方法,此方法通常用于与 COM 的交互操作,许多 .NET 枚举器抛出 NotSupportedException;
  2. 集合可以被 foreach, 不一定需要实现 IEnumerable 接口,有 GetEnumerator 方法即可。
  3. 一个集合类可以提供多个不同的 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 语句返回集合的一个元素,并移动到下一个元素,相当于同时维护 CurrentMoveNextyield break 可停止迭代。

使用 yield,编译器会创建一个状态机,用于实际维护 CurrentMoveNext

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》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇SMARTFORM报表程序设计(2)Python3 StringIO和BytesIO下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

select2的多选下拉框上传

1.加入multiple: true,属性实现多选下拉框样式 2.下拉框选择后的值是数组类型不要经过数据处理才能进行表单提交 提交的时候原下拉框所在的标签不提交,而是将多选后的值存入页面中的一个隐藏标签中,将这个隐藏标签进行提交   提交时获取选中的下拉框的所有的值,并遍历这些值,将这些值分别以(值 逗号)的形式存入一个变量中,并在遍历完成后去掉最后一个...

c++11の关联容器

一、关联容器 C++的容器类型可以分为顺序容器和关联容器两大类。对于关联容器,主要有map和set,对于这两种,根据不同的维度,衍生出了8种容器 map                                      //值对 set                                         //仅有值 multimap ...

回溯法 | 旅行商问题(TSP问题)

学习链接: 回溯法解旅行商问题(TSP)、贪心算法:旅行商问题(TSP) 今天早上做了无数个梦,然后被紧紧地吸附在床上。挣扎一番后爬起来,已经是9点了。然后我开始研究旅行商问题。 在一个无向图中找到一个可以遍历所有节点的一个最短回路。理论上说可以用全排列列出所有解的下标,然后一个一个试,时间复杂度o(n!)。但是可以用回溯法,用【约束函数】(constr...

Spark聚合操作:combineByKey()

Spark中对键值对RDD(pairRDD)基于键的聚合函数中,都是通过combineByKey()实现的。 它可以让用户返回与输入数据类型不同的返回值(可以自己配置返回的参数,返回的类型) 首先理解:combineByKey是一个聚合函数,实际使用场景比如,对2个同学的3门考试科目成绩,分别求出他们的平均值。 (也就是对3门考试成绩进行聚合,用一个平均数...

hash表的理解

哈希表 先从数组说起 任何一个程序员,基本上对数组都不会陌生,这个最常用的数据结构,说到它的优点,最明显的就是两点: 简单易用,数组的简易操作甚至让大多数程序员依赖上了它,在资源富足的情况下,我们甚至会无意识地忽略其它更适用的数据结构而使用数组(别说你没这么干过..)。 查找的快速性,数组中查找元素可以直接通过下标进行定位,速度快。 我在刚开始写程序...

jQuery遍历,数组,集合

使用了jquery有段时间了,整理下jquery中的遍历问题。 1.jquery 遍历对象 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">< HTML>  <HEAD>   <TITLE> New Document </TIT...