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

     
    出处:http://www.ondotnet.com/pub/a/dotnet/2004/06/07/liberty.html
    PDF 浏览:http://www.tracefact.net/Document/Iterators-In-CSharp.pdf
  • 相关阅读:
    python实现单线程多任务非阻塞TCP服务端
    sudo命令
    ajax 未加载出数据时,显示loding,数据显示后,隐藏loading
    ie7 解决圆角,阴影问题
    学习JavaScript你必须掌握的8大知识点!
    es6 个人笔记
    原生js,实现单选框
    原生js开发简单复选框,点击变颜色,再点击取消颜色,三种解决方法
    es6对象转数组
    JS正则截取两个字符串之间及字符串前后内容的方法
  • 原文地址:https://www.cnblogs.com/boltwolf/p/3369348.html
Copyright © 2020-2023  润新知