1 迭代器介绍
在.NET中,迭代器模式被IEnumerator和IEnumerable及其对应的泛型接口所封装。如果一个类实现了IEnumerable接口,那么就能够被迭代;调用GetEnumerator方法将返回IEnumerator接口的实现,它就是迭代器本身。迭代器类似数据库中的游标,它是数据序列中的一个位置记录。迭代器只能向前移动,同一数据序列中可以有多个迭代器同时对数据进行操作。
迭代器可用于逐步迭代集合,例如列表和数组。迭代器方法或 get 访问器可对集合执行自定义迭代。 迭代器方法使用 yield return 语句返回元素,每次返回一个。 到达 yield return 语句时,会记住当前在代码中的位置。 下次调用迭代器函数时,将从该位置重新开始执行。通过 foreach 语句或 LINQ 查询从客户端代码中使用迭代器。
2 C#4.0迭代器接口具体实现
泛型实现:
using System.Runtime.CompilerServices;
namespace System.Collections.Generic
{
public interface IEnumerable<out T> : IEnumerable
{
IEnumerator<T> GetEnumerator();
}
}
非泛型实现:
using System.Runtime.InteropServices;
namespace System.Collections
{
[ComVisible(true)]
[Guid("496B0ABE-CDEE-11d3-88E8-00902754C43A")]
public interface IEnumerable
{
[DispId(-4)]
IEnumerator GetEnumerator();
}
}
再来看下IEnumerator的实现,它主要包括三个方法:Current,MoveNext和Reset。
using System.Runtime.InteropServices;
namespace System.Collections
{
// 支持对非泛型集合的简单迭代。
[ComVisible(true)]
[Guid("496B0ABF-CDEE-11d3-88E8-00902754C43A")]
public interface IEnumerator
{
// 获取集合中的当前元素。
object Current { get; }
// 如果枚举数已成功地推进到下一个元素,则为 true;如果枚举数传递到集合的末尾,则为 false。
bool MoveNext();
//将枚举数设置为其初始位置,该位置位于集合中第一个元素之前。
void Reset();
}
}
因此,迭代器方法或 get 访问器的返回类型可以是 IEnumerable、IEnumerable< T >、IEnumerator 或 IEnumerator< T >。
3 手写迭代器实现
为了加深对迭代器的理解,本文参考了yangecnu的博客(大家可以去看看大神的分享)。因此,自己也尝试手(zhao)写(ban)一个迭代器来感受一个最原始的气息。在这里, 假设我们需要实现一个基于环形缓冲的新的集合类型。我们将实现IEnumerable接口,使得用户能够很容易的利用该集合中的所有元素。我们的忽略其他细节,将注意力仅仅集中在如何实现迭代器上。集合将值存储在数组中,集合能够设置迭代的起始点,例如,假设集合有5个元素,你能够将起始点设为2,那么迭代输出为2,3,4,0,最后是1.代码示例如下:
迭代器实现类:
using System;
using System.Collections;
namespace TestIEnumerator
{
class IterationSample : IEnumerable
{
public Object[] values;
public Int32 startingPoint;
public IterationSample(Object[] values, Int32 startingPoint)
{
this.values = values;
this.startingPoint = startingPoint;
}
public IEnumerator GetEnumerator()
{
//throw new NotImplementedException();
return new IterationSampleEnumerator(this);
}
}
class IterationSampleEnumerator : IEnumerator
{
IterationSample parent;//迭代的对象 #1
Int32 position;//当前游标的位置 #2
internal IterationSampleEnumerator(IterationSample parent)
{
this.parent = parent;
position = -1;// 数组元素下标从0开始,初始时默认当前游标设置为 -1,即在第一个元素之前, #3
}
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 bool MoveNext()
{
if (position != parent.values.Length) //判断当前位置是否为最后一个,如果不是游标自增 #4
{
position++;
}
return position < parent.values.Length;
}
public void Reset()
{
position = -1;//将游标重置为-1 #7
}
}
}
主程序测试类Program.cs:
using System;
namespace TestIEnumerator
{
class Program
{
static void Main(string[] args)
{
object[] values = { "a", "b", "c", "d", "e" };
//从数组指定的开始位置进行迭代
IterationSample collection = new IterationSample(values, 3);
foreach (object x in collection)
{
Console.Write(x + " ");
}//print "d e a b c"
Console.ReadKey();
}
}
}
要实现一个简单的迭代器需要手动写这么多的代码:需要记录迭代的原始集合#1,记录当前游标位置#2,返回元素时,根据当前游标和数组定义的起始位置设置定迭代器在数组中的位置#6。初始化时,将当前位置设定在第一个元素之前#3,当第一次调用迭代器时首先需要调用MoveNext,然后再调用Current属性。在游标自增时对当前位置进行条件判断#4,使得即使当第一次调用MoveNext时没有可返回的元素也不至于出错#5。重置迭代器时,我们将当前游标的位置还原到第一个元素之前#7。
4 通过yield语句实现迭代
4.1 实现返回值为IEnumerable类型的方法
通过迭代器方法来实现迭代,一般用于函数方法中,具体表现为:通过yield 关键字来实现返回值为IEnumerable类型的方法,示例代码如下:
using System;
using System.Collections.Generic;
namespace TestIEnumerator
{
class Program
{
public static IEnumerable<int>EvenSequence(int firstNumber, int lastNumber)
{
// Yield even numbers in the range.
for (int number = firstNumber; number <= lastNumber; number++)
{
if (number % 2 == 0)
{
//如果实现的是非泛型版本的接口,迭代块返的yield type是Object类型,
//否则返回的是相应的泛型类型。
//此处的返回值类型为int类型
yield return number;
}
static void Main(string[] args)
{
foreach (int number in EvenSequence(5, 18))
{
Console.Write(numbe // Output: 6 8 10 12 14 16 18
Console.ReadKey();
}
}
4.2 实现返回值为IEnumerator类型的方法
迭代的集合类DaysOfTheWeek 。首先,被迭代的集合类DaysOfTheWeek 继承IEnumerable接口。然后,实现该接口下的GetEnumerator方法。由此,即可实现迭代的目的,无需理会底层IEnumerator 接口的3个实现方法:Current()、MoveNext()和Reset()。示例代码如下:
using System;
using System.Collections;
using System.Collections.Generic;
namespace TestIEnumerator
{
class Program
{
public class DaysOfTheWeek : IEnumerable
{
private string[] days = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
public IEnumerator GetEnumerator()
{
for (int index = 0; index < days.Length; index++)
{
// Yield each day of the week.
yield return days[index];
}
}
}
static void Main(string[] args)
{
DaysOfTheWeek days = new DaysOfTheWeek();
foreach (string day in days)
{
Console.Write(day + " ");
}
// Output: Sun Mon Tue Wed Thu Fri Sat
Console.ReadKey();
}
}
}
五 小结
迭代器的实现有3方式:1.自己手动实现;2.可以通过实现返回值IEnumerable类型的方法;3迭代集合类时,.通过实现IEnumerable接口下的GetEnumerator方法。下一篇将介绍泛型集合的迭代器使用,有兴趣的可以继续浏览一下。