http://msdn.microsoft.com/msdnmag/issues/06/00/C20/default.aspx#S1
使用匿名方法、迭代器和局部类书写优雅的C#代码
就如在Visual C#® 2005中那样,C#语言爱好者将会发现Visual Studio® 2005也给C#带来了很多激动人心的新特性,例如泛型、迭代器、局部类,还有匿名方法。虽然泛型是讨论得最多的特性,特别是在那些熟悉模板的C++开发人员中,其它新特性对于你的Microsoft® .NET开发兵工厂来说也是非常重要的。相比先前的C#版本这些重要的特性和语言的附增物将会全面地提升你的生产力,让你更快的写出更加清晰的代码。
迭代器Iterators
在C#1.0中,你能在如数组和集合等数据结构上使用foreach循环来进行迭代:
1
string[] cities =
{"New York","Paris","London"};
2
3
foreach(string city in cities)
4
5

{
6
7
Console.WriteLine(city);
8
9
}
10
11
事实上你可以你可以在foreach循环中使用任何自定义的数据集合,只要那集合类型实现了一个GetEnumerator方法,该方法返回一个IEnumerator接口。通常通过实现IEnumerable接口来达到该目的:
1
public interface IEnumerable
2
3

{
4
5
IEnumerator GetEnumerator();
6
7
}
8
9
public interface IEnumerator
10
11

{
12
13
object Current
{get;}
14
15
bool MoveNext();
16
17
void Reset();
18
19
}
20
21
通过实现IEnumerable接口而用来在一个集合之上进行迭代的类,通常被提供作为集合类型的一个嵌套类而被迭代。这个迭代器类型维持迭代的状态。通常把嵌套类作为一个枚举器要更加好,因为它可以访问包含它的类的所有私有成员。当然,这是迭代器设计模式,它对将要进行迭代的客户端屏蔽了底层数据结构的实际实现细节,使得客户端对多种数据结构能用同样的迭代逻辑,就如图1中所示。

图1 迭代器设计模式
另外,因为每个迭代器维持各自的迭代状态,所以多客户能并发执行各自的迭代。像数组(Array)和队列(Quene)这样的数据结构通过实现IEnumerable接口来支持箱外迭代,在foreach循环中生成的代码仅仅包含了一个通过调用类的GetEnumerator方法得到的IEnumerator对象,在一个while循环中通过不断调用它的MoveNext方法和Current属性对集合进行迭代。如果你需要显式地对集合进行迭代,那么你能直接使用IEnumerator(没有foreach语句)。
但是用这种方法有一些问题。首先,如果集合包含值类型,获取其中的元素就需要对他们进行装箱和拆箱工作,因为IEnumerator.Current返回的是一个对象。这样的结果就是潜在的性能降级,还有在被管理的堆上压力增大。即使集合包含的是引用类型,你仍然会导致把对象向下转型的性能损失。然而,许多开发人员都不熟悉,在C#1.0中,你可以不通过实现IEnumerator或者IEnumerable接口就可以真正地实现适合于foreach循环的迭代器模式。编译器将调用强类型版本,避免转型和装箱,结果就是,即使在C#1.0版本中,也有可能不引起性能损失。
为了更好地阐明这种解决方案以及更加容易实现它,Microsoft .NET Framework 2.0在System.Collections.Generic名称空间定义了泛型、类型安全的IEnumerable<T> 和 IEnumerator<T>接口:
1
public interface IEnumerable<T> : IEnumerable
2
3

{
4
5
IEnumerator<T> GetEnumerator();
6
7
}
8
9
public interface IEnumerator<T> : IEnumerator,IDisposable
10
11

{
12
13
T Current
{get;}
14
15
}
16
17
因为泛型接口是派生自非泛型的,所以任何预期使用老接口的遗留客户端,它也能和一个支持新接口的新集合一起工作,这产生的副作用就是在你的集合代码上你必须使用显式接口实现,因为你不能仅仅基于返回类型而重载方法。
在图2中的代码展示了一个简单的实现了接口IEnumerable<string>的城市集合,还有在图3显示了当跨越foreach循环的代码时编译器怎样使用那个接口。在图2中,该实现使用了一个叫做MyEnumerator的嵌套类,它接受一个被用来枚举的集合的引用作为构造函数的参数。MyEnumerator类清楚地知道城市集合的实现细节,在该范例中是一个数组。这个MyEnumerator类在m_Current成员变量中维持当前的迭代状态,它被用作数组中的索引。也注意到在图2中非泛型方法IEnumerable.GetEnumerator 和 IEnumerator.Current属性把它们的实现委托给新接口的泛型方法。
第二个更加困难的问题是实现迭代。虽然对于简单情况,实现迭代是简单而直接的(就如 图2所示)。但是对更加高级的数据结构,例如二进制树,那就是挑战了,因为二进制树需要递归往复并在整个递归中维持迭代状态。此外,如果你要求多种迭代选项,例如在链表上的从头至尾和从尾至头迭代,这种适应于链表的代码将会变得臃肿,因为它需要实现多种迭代器。这的确就是C#2.0迭代器被设计来解决的问题。使用迭代器,你能让C#编译器为你生成IEnumerator 或者 IEnumerator<T>的实现。C#编译器能自动生成一个嵌套类来维持迭代状态。你能在一个泛型集合或者一特定类型集合上使用迭代器。所有你需要做的就是告诉编译器在每次迭代中输出什么。与手工提供一个迭代器一样,你需要暴露一个GetEnumerator方法,典型地你通过实现IEnumerable 或 IEnumerable<T>来完成。
使用新的C#yield返回语句你告诉编译器输出什么。例如,这儿就是在城市集合中怎样使用C#迭代器代替图2中的手工实现。
1
public class CityCollection : IEnumerable<string>
2
3

{
4
5
string[] m_Cities =
{"New York","Paris","London"};
6
7
IEnumerator<string> IEnumerable<string>.GetEnumerator()
8
9
{
10
11
for(int i = 0; i<m_Cities.Length; i++)
12
13
yield return m_Cities[i];
14
15
}
16
17
IEnumerator IEnumerable.GetEnumerator()
18
19
{
20
21
return ((IEnumerable<string>)this)GetEnumerator();
22
23
}
24
25
}
26
27
你也能在非泛型集合上使用C#迭代器:
public class CityCollection : IEnumerable



{


string[] m_Cities =
{"New York","Paris","London"};

public IEnumerator GetEnumerator()


{

for(int i = 0; i<m_Cities.Length; i++)

yield return m_Cities[i];

}

}


另外,你能在纯泛型的集合上使用C#迭代器,如图4中所显示的。当使用一个泛型集合和迭代器时,编译器从声明集合时使用到的类型就知道,在foreach循环中对于IEnumerable<T>接口用到的确切的类型是什么,在本范例中是string。
LinkedList<int,string> list = new LinkedList<int,string>();
// Some initialization of list, then
foreach(string item in list)
{
Trace.WriteLine(item);
}
这与任何派生自一个泛型接口的派生类都是类似的。
如果你因某些原因想中途停止迭代,使用yield语句中断语句,例如,下面的迭代器仅产生1、2和3这三个值:
public IEnumerator<int> GetEnumerator()
{
for(int i = 1; i <5; i++)
{
yield return i;
if(i > 2)
yield break;
}
}
你的集合能轻松地暴露多迭代器,每个都可以被用来以不同地方式遍历集合。例如,可以提供一个类型为IEnumerable<string>的叫做Reverse的属性,以便能以倒转的顺序遍历CityCollection类。
public class CityCollection
{
string[] m_Cities = {"New York","Paris","London"};
public IEnumerable<string> Reverse
{
get
{
for(int i=m_Cities.Length-1; i>= 0; i--)
yield return m_Cities[i];
}
}
}
然后在foreach循环中使用Reserve属性:
CityCollection collection = new CityCollection();
foreach(string city in collection.Reverse)
{
Trace.WriteLine(city);
}
什么地方使用以及怎样使用yield返回语句是有一些限制的。包含yield返回语句的方法或属性不能同时包含一个普通返回语句,因为那样不能正确地中断迭代。你不能在一个匿名方法中使用yield返回语句,同样在try语句中有它(也不能在catch或者finally块中).
迭代器实现
编译器生成嵌套类维持迭代状态。当迭代器第一次在foreach循环中(或者在直接的迭代代码中)被调用时,编译器为GetEnumerator生成的代码创建一个处于重置状态的迭代器对象(一个嵌套类的实例),每次foreach循环和调用迭代器的MoveNext方法时,它在先前的yield返回语句释放的地方开始执行。只要foreach循环在执行,迭代器就维持它的状态,然而,迭代器对象(还有它的状)不能持续跨越所有foreach循环,因此,再次调用foreach循环是安全的,因为你得到的是一个新的迭代器对象并开始一个新的迭代。
但是嵌套迭代器该如何实现以及如何管理它的状态呢?编译器把一个标准方法转化成一个被设计能供多次调用且使用一个简单状态机可以在先前的yield返回语句后恢复执行的方法。编译器甚至能足够精确地能以yield返回语句出现的次序来连接它们:
public class CityCollection : IEnumerable<string>
{
IEnumerator<string> IEnumerable<string>.GetEnumerator()
{
yield return "New York";
yield return "Paris";
yield return "London";
}
IEnumerator IEnumerable.GetEnumerator() {
}
}
让我们在下面几行代码中的看看类的IEnumerable<string>.GetEnumerator方法:
public class MyCollection : IEnumerable<string>
{
IEnumerator<string> IEnumerable<string>.GetEnumerator()
{
//Some iteration code that uses yield return
}
IEnumerator IEnumerable.GetEnumerator() {
}
}
当编译器遇到像这样一个包含yield返回语句的类成员,它导入一个嵌套类的定义,该类的名称是”GetEnumerator”再加上一个惟一由字母和数组组成的字符串。就如图5所示的C#伪代码中那样。(你应该记住编译器生成的类和字段的名称是不固定的,在将来的版本中会改变,你不能试图用反射来得到那些实现细节和期望每次都得到一致的结果。)
嵌套类实现从类成员返回的相同的IEnumerable 或 IEnumerable<T>接口。编译器用嵌套类实例化来替换类成员中的代码,把返回给集合的一个引用分派给嵌套类,类似在图2中那种手工实现。嵌套类是真正提供IEnumerator 或IEnumerator<T>接口实现的类。