【转】【C#】C# 迭代器

时间:2022-03-19 05:47:32

  迭代器模式是设计模式中行为模式(behavioral pattern)的一个例子,他是一种简化对象间通讯的模式,也是一种非常容易理解和使用的模式。简单来说,迭代器模式使得你能够获取到序列中的所有元素而不用关心是其类型是array,list,linked list或者是其他什么序列结构。这一点使得能够非常高效的构建数据处理通道(data pipeline)--即数据能够进入处理通道,进行一系列的变换,或者过滤,然后得到结果。事实上,这正是LINQ的核心模式。

在.NET中,迭代器模式被IEnumerator和IEnumerable及其对应的泛型接口所封装。如果一个类实现了IEnumerable接口,那么就能够被迭代;调用GetEnumerator方法将返回IEnumerator接口的实现,它就是迭代器本身。迭代器类似数据库中的游标,他是数据序列中的一个位置记录。迭代器只能向前移动,同一数据序列中可以有多个迭代器同时对数据进行操作。

在C#1中已经内建了对迭代器的支持,那就是foreach语句。使得能够进行比for循环语句更直接和简单的对集合的迭代,编译器会将foreach编译来调用GetEnumerator和MoveNext方法以及Current属性,如果对象实现了IDisposable接口,在迭代完成之后会释放迭代器。但是在C#1中,实现一个迭代器是相对来说有点繁琐的操作。C#2使得这一工作变得大为简单,节省了实现迭代器的不少工作。

接下来,我们来看如何实现一个迭代器以及C#2对于迭代器实现的简化,然后再列举几个迭代器在现实生活中的例子。

1. C#1:手动实现迭代器的繁琐

假设我们需要实现一个基于环形缓冲的新的集合类型。我们将实现IEnumerable接口,使得用户能够很容易的利用该集合中的所有元素。我们的忽略其他细节,将注意力仅仅集中在如何实现迭代器上。集合将值存储在数组中,集合能够设置迭代的起始点,例如,假设集合有5个元素,你能够将起始点设为2,那么迭代输出为2,3,4,0,最后是1.

为了能够简单展示,我们提供了一个设置值和起始点的构造函数。使得我们能够以下面这种方式遍历集合:

object[] values = { "a", "b", "c", "d", "e" }; IterationSample collection = new IterationSample(values, 3); foreach (object x in collection) { Console.WriteLine(x); }

由于我们将起始点设置为3,所以集合输出的结果是d,e,a,b及c,现在,我们来看如何实现 IterationSample 类的迭代器:

class IterationSample : IEnumerable { Object[] values; Int32 startingPoint; public IterationSample(Object[] values, Int32 startingPoint) { this.values = values; this.startingPoint = startingPoint; } public IEnumerator GetEnumerator() { throw new NotImplementedException(); } }

class IterationSampleEnumerator : IEnumerator { IterationSample parent;//迭代的对象 #1 Int32 position;//当前游标的位置 #2 internal IterationSampleEnumerator(IterationSample parent) { this.parent = parent; position = -1;// 数组元素下标从0开始,初始时默认当前游标设置为 -1,即在第一个元素之前, #3 } public bool MoveNext() { if (position != parent.values.Length) //判断当前位置是否为最后一个,如果不是游标自增 #4 { position++; } return position < parent.values.Length; } public object Current { get { if (position == -1 || position == parent.values.Length)//第一个之前和最后一个自后的访问非法 #5 { throw new InvalidOperationException(); } Int32 index = position + parent.startingPoint;//考虑自定义开始位置的情况 #6 index = index % parent.values.Length; return parent.values[index]; } } public void Reset() { position = -1;//将游标重置为-1 #7 } }

  要实现一个简单的迭代器需要手动写这么多的代码:需要记录迭代的原始集合#1,记录当前游标位置#2,返回元素时,根据当前游标和数组定义的起始位置设置定迭代器在数组中的位置#6。初始化时,将当前位置设定在第一个元素之前#3,当第一次调用迭代器时首先需要调用MoveNext,然后再调用Current属性。在游标自增时对当前位置进行条件判断#4,使得即使当第一次调用MoveNext时没有可返回的元素也不至于出错#5。重置迭代器时,我们将当前游标的位置还原到第一个元素之前#7。