C# 语法书 之 <1> 迭代器

这个系列的主要目的是尽量能覆盖C# 1.1之后的语法更新,以便让大家能够熟悉C# 2.0到4.0的语法特性,以提高编程效率,这里我忽略了一些诸如泛型、LINQ等等需要大章节才能阐述清楚的东西,原因是关注这些知识点的文章比比皆是,我所要写的语法都是一些比较小的,容易被人所忽略的地方。如果我有任何遗漏或者错误的地方,请给我个站内消息


1. 迭代器 适用范围:c# 2.0之后、

C# 1.0开始提供了非常方便的foreach循环,只要一个数据集实现了一个无参数的,返回Enumerator的GetEnumerator方法(甚至不需要实现任何接口),就可以在foreach循环中遍历数据集中的每个元素。大部分情况下,我们将数据存入某个Collection类,利用Collection类的GetEnumerator方法,就可以很方便地使用Foreach循环。

但有些时候我们必须自己去实现Enumerator,比如说打印某年每个月的天数,为了体现OOP的原则,我们的程序逻辑(即对闰年的判断,很高兴C#提供了相关的方法)应该被封装起来。

[code]

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;
}
}
}

}


}
}
[/code]

这段代码较长但不难理解,它相当好地封装了程序逻辑,调用起来也相当简洁优雅:

[code]
DaysOfTheMonth enu = new DaysOfTheMonth(1981);
foreach( int days in enu)
Console.Out.WriteLine(days);
[/code]

我们也看到实现Enumerator的过程未免过于复杂,一旦我们需要多个Enumerator来进行逆序,正序,奇偶数序迭代,代码就会相当繁杂。

C# 2.0之后新的yield关键字使得这个过程变得轻松简单。

[code]
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];
}
}

}
[/code]

这就是C# 2.0 中的迭代器,它使您能够方便地在类或结构中支持 foreach 迭代,而不必实现整个 IEnumerable 接口。

迭代器的返回类型必须为 IEnumerable、IEnumerator、IEnumerable<T> 或 IEnumerator<T>。当编译器检测到迭代器时,它将自动生成 IEnumerable 或 IEnumerable<T> 接口的 Current、MoveNext 和 Dispose 方法。

yield 关键字用于指定返回的值。到达 yield return 语句时,会保存当前位置。下次调用迭代器时将从此位置重新开始执行。而yield break则可以中止迭代,下面的代码有助于理解这点。

[code]
public class CityCollection : IEnumerable<string>
{
public IEnumerator<string> GetEnumerator()
{
yield return "New York";
yield return "Paris";
yield return "London";
}
}

[/code]

会依次打印出"New York", "Paris" "London". 有时候迭代器与迭代一起可以产生奇妙的效果,但也会耗费大量的内存。
[code]

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;
}
}
}

[/code]


foreach语句会隐式地调用集合的无参的GetEnumerator方法來得到一個迭代器。一个集合类中只能定义一个这样的无参的GetEnumerator方法,不过我们也可以在类中实现多个迭代器,但每个迭代器都必须像任何类成员一样有唯一的名称。

[code]
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];
}
}
}
}

[/code]

TopToBottom属性的get访问器只返回this,因为Stack本身就是一个可枚举类型。BottomToTop屬性使用C#迭代器返回了另一个可枚举接口。

实现IEnumerable接口的类看起来非常象一个枚举器工厂类,每次都产生一个独立的IEnumerator类。

[code]
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();
}
}
}
[/code]

上面的代码打印了一个从1到10的乘法表。注意FromTo方法只被调用了一次用来产生实现了IEnumerable接口的变量e。而e.GetEnumerator()被调用了多次(通过foreach语句)來产生多个相同的迭代器。這些迭代器都封裝了FromTo声明中指定的代碼。注意,迭代其代码的时候改变了from参数。但是,迭代器是独立的,因此对于from参数和to参数,每個迭代器有它自己的一份拷贝。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值