浏览 3760 次
锁定老帖子 主题:C# 语法书 之 <1> 迭代器
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2009-06-09
最后修改:2010-04-07
1. 迭代器 适用范围:c# 2.0之后、 C# 1.0开始提供了非常方便的foreach循环,只要一个数据集实现了一个无参数的,返回Enumerator的GetEnumerator方法(甚至不需要实现任何接口),就可以在foreach循环中遍历数据集中的每个元素。大部分情况下,我们将数据存入某个Collection类,利用Collection类的GetEnumerator方法,就可以很方便地使用Foreach循环。 但有些时候我们必须自己去实现Enumerator,比如说打印某年每个月的天数,为了体现OOP的原则,我们的程序逻辑(即对闰年的判断,很高兴C#提供了相关的方法)应该被封装起来。 using System; using System.Collections; namespace SharpDemo{ public class DaysOfTheMonth { public DaysOfTheMonth(int year) { this.year = year; } int year = 1900; public System.Collections.IEnumerator GetEnumerator() { return new DaysOfMonthEnumrator(this.year); } class DaysOfMonthEnumrator : IEnumerator { private int[] days = new int[] { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; public DaysOfMonthEnumrator(int year) { if (DateTime.IsLeapYear(year)) { days[1] = 29; } } private int index = -1; public IEnumerator GetEnumerator() { return this; } public void Reset() { index = -1; } public object Current { get { if (this.index < this.days.Length) { return this.days[this.index]; } else { throw new IndexOutOfRangeException(); } } } public bool MoveNext() { if (this.days.Length == 0) { return false; } else { this.index += 1; if (this.index == this.days.Length) { return false; } else { return true; } } } } } } 这段代码较长但不难理解,它相当好地封装了程序逻辑,调用起来也相当简洁优雅: DaysOfTheMonth enu = new DaysOfTheMonth(1981); foreach( int days in enu) Console.Out.WriteLine(days); 我们也看到实现Enumerator的过程未免过于复杂,一旦我们需要多个Enumerator来进行逆序,正序,奇偶数序迭代,代码就会相当繁杂。 C# 2.0之后新的yield关键字使得这个过程变得轻松简单。 public class DaysOfMonth { int year = 1900; int[] days = new int[] { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; public DaysOfMonth2(int year) { this.year = year; if (DateTime.IsLeapYear(year)) { days[1] = 29; } } public IEnumerator GetEnumerator() { for (int i = 0; i < this.days.Length; i++) { yield return this.days[i]; } } } 这就是C# 2.0 中的迭代器,它使您能够方便地在类或结构中支持 foreach 迭代,而不必实现整个 IEnumerable 接口。 迭代器的返回类型必须为 IEnumerable、IEnumerator、IEnumerable<T> 或 IEnumerator<T>。当编译器检测到迭代器时,它将自动生成 IEnumerable 或 IEnumerable<T> 接口的 Current、MoveNext 和 Dispose 方法。 yield 关键字用于指定返回的值。到达 yield return 语句时,会保存当前位置。下次调用迭代器时将从此位置重新开始执行。而yield break则可以中止迭代,下面的代码有助于理解这点。 public class CityCollection : IEnumerable<string> { public IEnumerator<string> GetEnumerator() { yield return "New York"; yield return "Paris"; yield return "London"; } } 会依次打印出"New York", "Paris" "London". 有时候迭代器与迭代一起可以产生奇妙的效果,但也会耗费大量的内存。 IEnumerable<T> ScanInOrder(Node<T> root) { if(root.LeftNode != null) { foreach(T item in ScanInOrder(root.LeftNode)) { yield return item; } } yield return root.Item; if(root.RightNode != null) { foreach(T item in ScanInOrder(root.RightNode)) { yield return item; } } } foreach语句会隐式地调用集合的无参的GetEnumerator方法來得到一個迭代器。一个集合类中只能定义一个这样的无参的GetEnumerator方法,不过我们也可以在类中实现多个迭代器,但每个迭代器都必须像任何类成员一样有唯一的名称。 using System.Collections.Generic; public class Stack<T>: IEnumerable<T> { T[] items; int count; public void Push(T data) {...} public T Pop() {...} public IEnumerator<T> GetEnumerator() { for (int i = count – 1; i >= 0; --i) { yield return items[i]; } } public IEnumerable<T> TopToBottom { get { return this; } } public IEnumerable<T> BottomToTop { get { for (int i = 0; i < count; i++) { yield return items[i]; } } } } TopToBottom属性的get访问器只返回this,因为Stack本身就是一个可枚举类型。BottomToTop屬性使用C#迭代器返回了另一个可枚举接口。 实现IEnumerable接口的类看起来非常象一个枚举器工厂类,每次都产生一个独立的IEnumerator类。 using System; using System.Collections.Generic; class Test { static IEnumerable<int> FromTo(int from, int to) { while (from <= to) yield return from++; } static void Main() { IEnumerable<int> e = FromTo(1, 10); foreach (int x in e) { foreach (int y in e) { Console.Write("{0,3} ", x * y); } Console.WriteLine(); } } } 上面的代码打印了一个从1到10的乘法表。注意FromTo方法只被调用了一次用来产生实现了IEnumerable接口的变量e。而e.GetEnumerator()被调用了多次(通过foreach语句)來产生多个相同的迭代器。這些迭代器都封裝了FromTo声明中指定的代碼。注意,迭代其代码的时候改变了from参数。但是,迭代器是独立的,因此对于from参数和to参数,每個迭代器有它自己的一份拷贝。 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2009-06-10
积分又50了,该接je的老底了啊
|
|
返回顶楼 | |
发表时间:2009-06-11
lz给深入讲讲委托吧。。。
java转c#的人,总感觉委托比其他东西难理解些。。。 |
|
返回顶楼 | |
发表时间:2009-06-12
NightTree 写道 lz给深入讲讲委托吧。。。
java转c#的人,总感觉委托比其他东西难理解些。。。 期待中,我只知道他用的是函数指针 c转c#的人 |
|
返回顶楼 | |
发表时间:2009-06-14
再给一段我实际工作中的例子,不借助yield return,代码就必须借助IList,其结果是无法干净优雅
public static IEnumerable<String> GetListOfMountPoints() { StringBuilder buffer = new StringBuilder((int)N, (int)N); IntPtr hVolumn = Volume.FindFirstVolume(buffer, N); do { String volumn = buffer.ToString(); IntPtr hMountPoint = Volume.FindFirstVolumeMountPoint(volumn, buffer, N); do { yield return buffer.ToString(); } while (Volume.FindNextVolumeMountPoint(hMountPoint, buffer, N)); Volume.FindVolumeMountPointClose(hMountPoint); } while (Volume.FindNextVolume(hVolumn, buffer, N)); Volume.FindVolumeClose(hVolumn); } |
|
返回顶楼 | |