一 枚举器和可枚举类型
当我们为数组使用foreach语句时,这个语句为我们依次取出了数组中的每一个元素。
var arrInt = new int[] { 11, 12, 13, 14 }; foreach (var item in arrInt) { Console.WriteLine(item); }
原因是数组实现了IEnumerable接口,接口提供了一个GetEnumerator方法可以获取一个实现了IEnumerator接口的枚举器对象。
枚举器可以依次返回请求的数组中的元素。
实现了IEnumerable接口类型叫做可枚举类型。数组是可枚举类型。
public interface IEnumerable { // 摘要: 返回循环访问集合的枚举器。 // 返回结果:一个可用于循环访问集合的 System.Collections.IEnumerator 对象。 IEnumerator GetEnumerator(); }
foreach语句设计用来和可枚举类型一起使用。只要给它的遍历对象是可枚举类型,它就会执行如下行为:
- 通过调用GetEnumerator方法获取对象的枚举器;
- 从枚举器中请求每一项并把它作为迭代变量。
二 IEnumerator接口
IEnumerator接口的定义如下:
public interface IEnumerator { // 摘要:获取集合中位于枚举数当前位置的元素 // 返回结果:集合中位于枚举数当前位置的元素。 object Current { get; } // 摘要:将枚举数推进到集合的下一个元素。 // 返回结果:如果枚举数已成功地推进到下一个元素,则为 true;如果枚举数传递到集合的末尾,则为 false。 // 异常:T:System.InvalidOperationException:创建枚举器后,已修改该集合。 bool MoveNext(); // 摘要:将枚举数设置为其初始位置,该位置位于集合中第一个元素之前。 // 异常: T:System.InvalidOperationException: 创建枚举器后,已修改该集合。 void Reset(); }
有了枚举器,就可以使用MoveNext和Current来模仿foreach循环:
int[] arrInt = new int[] { 11, 12, 13, 14 }; foreach (var item in arrInt) { Console.WriteLine(item); } var enumerator = arrInt.GetEnumerator(); while (enumerator.MoveNext()) { var item = (int)enumerator.Current; Console.WriteLine(item); }
下面是一个使用IEnumerator和IEnumerable的小例子:
class Program { static void Main(string[] args) { var myColor = new MyColors(); foreach (var item in myColor) { Console.WriteLine(item); } } } class ColorEnumeraotr : IEnumerator { string[] _colors; int _position = -1; public ColorEnumeraotr(string[] theColors) { _colors = new string[theColors.Length]; for (int i = 0; i < theColors.Length; i++) { _colors[i] = theColors[i]; } } public object Current { get { if (_position == -1) throw new InvalidOperationException(); if (_position >= _colors.Length) throw new InvalidOperationException(); return _colors[_position]; } } public bool MoveNext() { if (_position < _colors.Length - 1) { _position++; return true; } return false; } public void Reset() { _position = -1; } } class MyColors : IEnumerable { string[] Colors = new string[] { "red","blue","yellow","green","white"}; public IEnumerator GetEnumerator() { return new ColorEnumeraotr(Colors); } }
三 泛型枚举接口
非泛型接口的实现不是类型安全的,它返回object的引用,然后必须转化为实际类型。
所以实际上,在多数情况下,我们应该使用泛型版本的IEnumerable<T>和IEnumerator<T>。
泛型接口的枚举器是类型安全的,它返回实际类型的引用。
IEnumerable<T>接口的GetEnumerator方法返回实现IEnumerator<T>的枚举器对象。
实现IEnumerator<T>的枚举器类的Current属性,它返回T类型的对象。
四 迭代器
C#从2.0版本开始提供了更简单的创建枚举器和可枚举类型的方式。这种结构叫做迭代器。
迭代器块是有一个或多个yield语句的代码块。
迭代器块与其他代码块不同。其他块包含的语句是命令式的,也就是块中的语句依次执行,最后离开块。
而迭代器块是描述了希望编译器为我们创建的枚举器类的行为,迭代器块中的代码描述了如何枚举元素。
迭代器块有两个特殊语句:
- yield return : 指定了序列中返回的下一项;
- yield break : 指定在序列中没有其他项。
//产生枚举器的迭代器 public IEnumerator<string> IteratorMethod1() { yield return "red"; yield return "blue"; yield return "yellow"; } //产生可枚举类型的迭代器 public IEnumerable<string> IteratorMethod2() { yield return "red"; yield return "blue"; yield return "yellow"; }
4.1 使用迭代器来创建枚举器
下面代码演示如何使用迭代器来创建枚举器:
class Program { static void Main(string[] args) { var mc = new MyClass(); foreach (var item in mc) { Console.WriteLine(item); } } } class MyClass { public IEnumerator<string> GetEnumerator() { return MyEnumerator(); //返回枚举器 } //迭代器 public IEnumerator<string> MyEnumerator() { yield return "red"; yield return "blue"; yield return "yellow"; } }
4.2 使用迭代器来创建可枚举类型
下面代码演示如何使用迭代器来创建可枚举类型:
class Program { static void Main(string[] args) { var mc = new MyClass(); foreach (var item in mc) { Console.WriteLine(item); } foreach (var item in mc.MyEnumerable()) { Console.WriteLine(item); } } } class MyClass { public IEnumerator<string> GetEnumerator() { IEnumerable<string> myEnumerable = MyEnumerable(); return myEnumerable.GetEnumerator(); } public IEnumerable<string> MyEnumerable() { yield return "red"; yield return "blue"; yield return "yellow"; } }