枚举器和迭代器
1. 枚举器
枚举器(Enumerator)是一个只读的且只能在值序列上前移的游标。
任何具有MoveNext方法和Current属性的对象都被称作枚举器。
枚举器实现了下面的接口之一:
System.Collections.IEnumerator
System.Collections.Generic.IEnumerator
2. 可枚举对象
可枚举对象(Enumerable),它可以生成枚举器。
可枚举的对象可以是:
IEnumerable 或 IEnumerable的实现。
具有名为GetEnumerator 的方法并且返回值是一个枚举器 (Enumerator)对象
注意:IEnumerator 和 IEnumerable 定义在 System.Collections 里。
IEnumerator 和 IEnumerable 定义在 System.Collections.Generic 里。
//枚举器模板
class Enumerator
{
public IteratorVariableType Current{
get{
...}}
public bool MoveNext(){
...}
}
//可枚举类型模版
class Enumerable
{
public Enumerator GetEnumerator(){
...}
}
3. 迭代器
迭代器是枚举器的生产者。foreach语句是枚举器的消费者。
案例:使用迭代器来返回斐波那契数列(每个数字是前两个数字之和)
using System;
using System.Collections.Generic;
class Test
{
static void Main()
{
foreach(int fib in Fibs(6))
Console.Write(fib+" ");
}
//迭代器方法
static IEnumerable<int> Fibs(int fibCount)
{
for(int i =0,prevFib =1,curFib=1;i<fibCount;i++)
{
yield return prevFib;
int newFib = prevFib+curFib;
prevFib = curFib;
curFib = newFib;
}
}
}
//输出
1 1 2 3 5 8
4. 迭代器语义
迭代器是包含一个或者多个 yield 语句的方法,属性或者索引器。
//迭代器必须返回以下四个接口之一(否则编译器会产生相应错误):
//可枚举接口
System.Collections.IEnumerable
System.Collections.Generic.IEnumerable<T>
//枚举器接口
System.Collections.IEnumerator
System.Collections.Generic.IEnumerator<T>
//多个yield语句
class Test
{
static void Main()
{
foreach(string s in Foo())
Console.WriteLine(s); //输出 One,Two,Three
}
static IEnumerable<string> Foo()
{
yield return "One";
yield return "Two";
yield return "Three";
}
}
5. yield break 语句
yield break 语句表明迭代器块不再返回更多的元素,而是提前退出。
static IEnumerable<string> Foo(bool breakEarly)
{
yield return "One";
yield return "Two";
if(breakEarly)
yield break; //到这里就退出了
yield return "Three";
}
注意:yield return 语句不能出现在 try..catch..finally 块中,只能出现在try..finally中try块里面。
通常使用foreach或隐式销毁枚举器,但是如果显示使用枚举器,提前结束枚举而不销毁枚举器,绕过了finally块的执行。那么我们可以将枚举器显式包裹在using语句中来避免上述错误。
string firstElement = null;
var sequence = Foo();
using(var enumerator = sequence.GetEnumerator()) //使用using,会自动关闭一个持续流
if(enumerator.MoveNext())
firstElement = enumerator.Current;
6. 组合序列
迭代器有高度可组合性。迭代器模式的组合对LINQ非常重要。
class Program
{
static void Main(string[] args)
{
foreach (int fib in EvenNumbersOnly(Fibs(6)))
{
Console.WriteLine(fib);
}
}
static IEnumerable<int> Fibs(int fibCount)
{
for (int i = 0,prevFib = 1,curFib =1; i<fibCount; i++)
{
yield return prevFib;
int newFib = prevFib + curFib;
prevFib = curFib;
curFib = newFib;
}
}
static IEnumerable<int> EvenNumbersOnly(IEnumerable<int> sequence)
{
foreach (int x in sequence)
{
if ((x % 2) ==0)
{
yield return x;
}
}
}
}