c# 迭代器
最近重新看《深入理解C#》, 看到迭代器一章,算是笔记吧。
迭代器模式是行为模式的一种范例,行为模式是一种简化对象之间通信的设计模式。它允许你访问一个数据项序列中的所有元素,而无须关心序列是什么类型(数组,列表,链表,或任何其他类型)。
在.Net中,迭代器模式是通过IEnumerator和IEunmerable接口及他们的泛型等价物来封装的。如果某个类型实现了IEnumerable接口,就意味着它可以被迭代访问。
Class要实现IEunmerable,IEunmerable要实现返回值为IEnumerator的GetEnumerator方法。代码如下:
1 public class Iteration : IEnumerable 2 { 3 private readonly object[] _values;//要迭代的数组 4 private readonly int _startingPoint;//迭代的起始索引 5 6 public Iteration(object[] values, int startingPoint) 7 { 8 _values = values; 9 _startingPoint = startingPoint; 10 } 11 public IEnumerator GetEnumerator() 12 { 13 for (int i = 0; i < _values.Length; i++) 14 { 15 yield return _values[(i + _startingPoint)%_values.Length]; 16 } 17 18 } 19 }
其实上面的GetEnumerator()中的for循环可以用Linq自带的GetEnumerator()来实现更简单
public IEnumerator GetEnumerator() { return _values.Select((t, i) => _values[(i + _startingPoit) % _values.Length]).GetEnumerator();
}n
yield 是在c#2.0中加入的,主要是为了更加简便的实现迭代器,这句代码就是告诉c#编译器这不是一个普通方法,而是一个实现的迭代器块的方法。这个方法被声明为返回一个IEnumerator接口。yield其实就是一个语法糖,背后还是有一堆繁琐的操作来支持遍历,如果在c#1.0就需要自己去写这些了,上面 GetEnumerator 就相当于下面的c#1.0里的代码:
1 class Iterator:IEnumerator 2 { 3 private Iteration _parent; 4 private int _position; 5 6 public Iterator(Iteration parent) 7 { 8 _parent = parent; 9 _position = -1; 10 } 11 12 public bool MoveNext() 13 { 14 if (_position != _parent._values.Length) 15 { 16 _position++; 17 } 18 19 return _position < _parent._values.Length; 20 } 21 22 public void Reset() 23 { 24 _position = -1; 25 } 26 27 public object Current 28 { 29 get 30 { 31 if (_position == -1 || _position == _parent._values.Length) 32 { 33 throw new InvalidOperationException(); 34 } 35 int index = _position + _parent._startingPoint; 36 index = index%_parent._values.Length; 37 return _parent._values[index]; 38 39 } 40 } 41 }
可以看到这么简单的一个任务使用好多代码,而且这只是一个简单的例子,没有太多的状态需要跟踪,也没有尝试检查集合是否在两次迭代之中改变。实现一个简单的迭代器都需要花费这么大精力,所以很少有人在C#1中实现这个模式。而在C#2中yield的出现大大简化了迭代器的实现,所有的一切都交给编译器去做了。
下面来看一看迭代器的执行流程:
1 private static IEnumerable<int> CreateEnumerable() 2 { 3 Console.WriteLine("{0} 迭代开始", _padding); 4 5 for (int i = 0; i < 3; i++) 6 { 7 Console.WriteLine("{0} 第 {1} 次迭代", _padding, i); 8 yield return i; 9 Console.WriteLine("{0}下一次迭代", _padding); 10 } 11 12 Console.WriteLine("{0} 迭代最后一个值", _padding); 13 yield return -1; 14 Console.WriteLine("{0} 迭代结束", _padding); 15 }
1 private static string _padding = new string(' ', 30); 2 3 IEnumerable<int> iterable = CreateEnumerable(); 4 IEnumerator<int> iterator = iterable.GetEnumerator(); 5 Console.WriteLine("开始"); 6 7 while (true) 8 { 9 Console.WriteLine("开始调用 MoveNext()...."); 10 bool result = iterator.MoveNext(); 11 Console.WriteLine("....MoveNext result={0}", result); 12 if (!result) 13 { 14 break; 15 } 16 17 Console.WriteLine("当前值"); 18 Console.WriteLine("....Current result={0}", iterator.Current); 19 20 }
下面是输出结果:
可以看到:
在第一次调用MoveNext 之前, CreateEnumerable中的代码不会被调用.
所有的工作都在调用MoveNext的时候就完成了,获取Current的值不会执行任何代码.
在yield return的位置,代码就停止执行,在下一次调用MoveNext的时候又继续执行.
在同一个方法中的不同地方可以书写多个yield return 语句.
代码不会在最后的yield return处结束,相反,而是通过返回false的MoveNext调用来结束方法的执行.
如果想提前终止迭代怎么办呢,可以用yield break语句来实现,它可以让当前对MoveNext的调用返回false,实际上终止了迭代器的运行.
那么为什么要用迭代器呢,好多取数据其实直接用for循环就可以实现,iterator的优势是什么呢?
好处就是降低了数据结构的耦合性,将遍历一个序列的操作与此序列底层结构相分离.所以使用迭代器是面向对象封装性的体现。其实LINQ中定义的查询方法,如Select,Where等返回就是一个实现了IEnumerable接口的类型,而我们不用去关心具体的类型是什么,直接用foreach迭代就可以了.
posted on 2013-02-21 19:44 ZoeToString 阅读(263) 评论(0) 编辑 收藏 举报