标题：C#枚举器
术语表
Iterator：枚举器
如果你正在创建一个表现和行为都类似于集合的类，允许类的用户使用foreach语句对集合中的成员进行枚举将会是很方便的。这在C# 2.0中比 C# 1.1更容易实现一些。作为演示，我们先在 C# 1.1中为一个简单的集合添加枚举，然后我们修改这个范例，使用新的C#2.0 枚举构建方法。
我们将以创建一个简单化的List Box作为开始，它将包含一个8字符串的数组和一个整型，这个整型用于记录数组中已经添加了多少字符串。构造函数将对数组进行初始化并使用传递进来的参数填充它。
public ListBox(params string[] initialStrings)
{
strings = new String[8]; 
foreach (string s in initialStrings)
{
strings[ctr++] = s;
}
}
除此以外，ListBox类还需要一个Add方法(进行添加 string 的操作) 和 一个返回数组中字符串个数的方法。
public void Add(string theString)
{
strings[ctr] = theString;
ctr++;
}
public int GetNumEntries()
{
return ctr;
}
NOTE：实际开发中，通常使用ArrayList，而不是固定大小的数组。在这里为了程序简单就没有做数组下标越界的检测。
从感觉上看，ListBox像是一个集合，如果可以使用集合中通常使用的 foreach 循环来获取listBox中的所有字符串将会是非常便利的。如此的话，可以这样书写代码：
ListBox lb = new ListBox("a", "b", "c", "d", "e", "f", "g", "h");
foreach (string s in lb) {
Console.WriteLine(s);
}
但是，会得到这样一个错误：
“Iterator.ListBox”不包含“GetEnumerator”的公共定义，因此 foreach 语句不能作用于“Iterator.ListBox”类型的变量
想要使用foreach语句，还必须实现IEnumerable 接口。
这个接口只要求实现一个方法： GetEnumerator。这个方法必须返回一个实现了IEnumerator 接口的对象。除此以外，我们需要返回的这个对象不仅实现了IEnumerator，而且知道如何枚举ListBox对象。你将需要创建一个 ListBoxEmunerator(在下面描述)：
NOTE: IEnumerable 和 IEnumerator 是不同的接口，请不要搞混了。
public IEnumerator GetEnumerator()
{
return new ListBoxEnumerator();
}
现在，ListBox 可以使用 foreach 循环了：
ListBox lbt = new ListBox("Hello", "World");
lbt.Add("Who");
lbt.Add("Is");
lbt.Add("John");
lbt.Add("Galt");
foreach (string s in lbt)
{
Console.WriteLine("Value: {0}", s);
}
先是实例化这个ListBox ，并初始了两个字符串，随后又添加了四个。foreach循环接受ListBox实例，并且迭代它，依次返回字符串。输出是：
Hello
World
Who
Is
John
Galt
实现 IEnumerator 接口
注意到ListBoxEnumerator不仅需要实现IEnumerator接口，对于ListBox类它也需要一些特别了解；特别是，它必须可以获得ListBox的字符串数组并且遍历其所包含的字符串。IEnumerable 类和与其相关的 IEnumerator类之间的关系有一点微妙。实现IEnumerator接口的最好办法是在IEnumerable类里创建一个嵌套的IEnumerator类。
public class ListBox : IEnumerable
{
// 嵌套的私有ListBoxEnumerator类实现
private class ListBoxEnumerator : IEnumerator
{
// 代码实现...
}
// ListBox类的代码...
}
注意ListBoxEnumerator需要对它所嵌入的ListBox类的一个引用。你可以通过ListBoxEnumerator的构造函数来传递。
为了实现IEnumerator接口，ListBoxEnumerator需要两个方法：MoveNext和Reset，还有一个属性：Current。这些方法和属性的任务是创建一个状态机制，确保你可以在任何时候得知ListBox中的哪个元素是当前元素，并获得那个元素。
在这个例子中，这种状态机制是通过维护一个标明当前string的索引值来完成的，并且，你可以通过对外部类的string集合进行索引来返回这个当前的string。为了达到这个目标，你需要一个成员变量保存对于外部ListBox对象的引用，以及一个整型用于保存当前索引。
private ListBox lbt;
private int index;
每次Reset方法被调用的时候，index被置为 -1。
public void Reset()
{
index = -1;
}
每次MoveNext被调用的时候，外部类的数组检查时候已经到了末尾，如果是这样，方法返回false。如果集合中还有对象，index将增加，并且方法返回true。
public bool MoveNext()
{
index++;
if (index >= lbt.strings.Length)
{
return false;
}else
{
return true;
}
}
最后，如果MoveNext方法返回True，foreach循环将调用Current属性。ListBoxEnumerator的Current属性的实现是索引外部类(ListBox)中的集合，并且返回找到的对象(这个例子中，是一个字符串)。注意，返回一个Object是因为IEnumerator接口中Current属性的签名如此。
public object Current
{
get {
return(lbt[index]);
}
}
在1.1中，所有想要通过foreach循环来迭代的类都需要实现IEnumerable接口，于是，必须创建一个实现了IEnumerator的类。最糟的是，enumerator返回的值并不是类型安全的。记得Current属性返回一个Object对象；它仅仅简单的假设你所返回的值与foreach循环所期望的相符合。
C# 2.0 的解救办法
使用C# 2.0 这些问题如同五月末的雪般融化了。在这个例子的2.0版本中，我重写上面的列表，使用C# 2.0的两个新特性：泛型 和 枚举器。
我以重新定义实现IEumerable<string>的ListBox作为开始：
public class ListBox : IEnumerable<string>
这样做确定这个类可以在foreach循环中使用，同时确保迭代的值是string类型。
现在，从上个例子中挪去整个嵌套类，并且用下面的代码替换 GetEnumerator方法。
public IEnumerator<string> GetEnumerator()
{
foreach (string s in strings)
{
yield return s;
}
}
GetEnumerator方法使用了新的 yield 语句。yield语句返回一个表达式。yield语句仅在迭代块中出现，并且返回foreach语句所期望的值。那也就是，对GetEnumerator的每次调用都将会产生集合中的下一个字符串；所有的状态管理已经都为你做好了！
就这样了，你已经完成了。不需要为每个类型实现你自己的enumerator，不需要创建嵌套类。你已经移除了至少30行代码，并且极大地简化了你的代码。程序继续像期望的那样运行，但是状态管理不再是你的任务，所有的都为你做好了。更进一步，由枚举器所返回的值一定是string类型，如果你想要返回其他类型，你可以修改IEnumerable泛型语句，IEnumerable泛型语句将反射新类型。
关于Yield的更多内容
作为对上一节的一些说明，应该告诉你：实际上，你可以在yield语句块中yield一个以上的值。这样，下面的语句是完全正确的C#语句：
public IEnumerator GetEnumerator()
{
yield return "Who";
yield return " is";
yield return "John Galt?";
}
假设上面的代码位于一个名为foo的类中，你可以这样写：
foreach ( string s in new foo())
{
Console.Write(s);
}
输出结果将会是：
Who is John Galt?
如果你现在停下来思考一下，这些也是之前的代码所做的事。它遍历了自己的foreach循环，并且产生出它所找到的每个string字符串。