C#中的枚举器(转)

术语表

Iterator:枚举器(迭代器)

若是你正在建立一个表现和行为都相似于集合的类,容许类的用户使用foreach语句对集合中的成员进行枚举将会是很方便的。这在C# 2.0中比 C# 1.1更容易实现一些。做为演示,咱们先在 C# 1.1中为一个简单的集合添加枚举,而后咱们修改这个范例,使用新的C#2.0 枚举构建方法。html

咱们将以建立一个简单化的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;
}post

 

NOTE:实际开发中,一般使用ArrayList,而不是固定大小的数组。在这里为了程序简单就没有作数组下标越界的检测。.net

从感受上看,ListBox像是一个集合,若是可使用集合中一般使用的 foreach 循环来获取listBox中的全部字符串将会是很是便利的。如此的话,能够这样书写代码:htm

ListBox lb = new ListBox("a", "b", "c", "d", "e", "f", "g", "h");
foreach (string s in lb) {
    Console.WriteLine(s);
}对象

可是,会获得这样一个错误:blog

“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字符串。

 
出处: http://www.ondotnet.com/pub/a/dotnet/2004/06/07/liberty.html
PDF 浏览: http://www.tracefact.net/Document/Iterators-In-CSharp.pdf
相关文章
相关标签/搜索