术语表

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

 

C#中的枚举器(转)的更多相关文章

  1. ruby中迭代器枚举器的理解

    参考<ruby编程语言>5.3迭代器和可枚举对象 迭代器一个迭代器是一个方法,这个方法里面有yield语句,这个方法里的yield语句,与传递给这个方法的块进行数据传输 yield将数据传 ...

  2. C#中的枚举器

    更新记录 本文迁移自Panda666原博客,原发布时间:2021年6月28日. 一.先从可枚举类型讲起 1.1 什么是可枚举类型? 可枚举类型,可以简单的理解为: 有一个类,类中有挺多的数据,用一种统 ...

  3. C#图解教程 第十八章 枚举器和迭代器

    枚举器和迭代器 枚举器和可枚举类型 foreach语句 IEnumerator接口 使用IEnumerable和IEnumerator的示例 泛型枚举接口迭代器 迭代器块使用迭代器来创建枚举器使用迭代 ...

  4. JAVA中的数据结构——集合类(序):枚举器、拷贝、集合类的排序

    枚举器与数据操作 1)枚举器为我们提供了访问集合的方法,而且解决了访问对象的“数据类型不确定”的难题.这是面向对象“多态”思想的应用.其实是通过抽象不同集合对象的共同代码,将相同的功能代码封装到了枚举 ...

  5. C#中的foreach语句与枚举器接口(IEnumerator)及其泛型 相关问题

    这个问题从<C#高级编程>数组一节中的foreach语句(6.7.2)发现的. 因为示例代码与之前的章节连贯,所以我修改了一下,把自定义类型改为了int int[] bs = { 2, 3 ...

  6. C#2.0中使用yield关键字简化枚举器的实现

    我们知道要使用foreach语句从客户端代码中调用迭代器,必需实现IEnumerable接口来公开枚举器,IEnumerable是用来公开枚举器的,它并不实现枚举器,要实现枚举器必需实现IEnumer ...

  7. Python 中的枚举类型~转

    Python 中的枚举类型 摘要: 枚举类型可以看作是一种标签或是一系列常量的集合,通常用于表示某些特定的有限集合,例如星期.月份.状态等. 枚举类型可以看作是一种标签或是一系列常量的集合,通常用于表 ...

  8. for..in遍历,枚举器

    #pragma mark ------------for循环遍历集合中的元素------ //创建一个数组,包含5个字符串对象,倒序取出数组中的所有元素,并存储到另一可变数组中 NSArray *ar ...

  9. ruby迭代器枚举器

    迭代器一个迭代器是一个方法,这个方法里面有yield语句,使用了yield的方法叫做迭代器,迭代器并非一定要迭代,与传递给这个方法的块进行数据传输 yield将数据传给代码快,代码块再把数据传输给yi ...

随机推荐

  1. COJ 3018 求1~n之间的素数

    求1~n之间的素数 难度级别:A: 运行时间限制:1000ms: 运行空间限制:256000KB: 代码长度限制:2000000B 试题描述  素数是大于1,且除1和本身以外不能被其他整数所整除的数. ...

  2. -_-#ueditor编辑器chrome浏览器下只能复制最后一行

    被过滤掉了

  3. apache 创建虚拟目录

    <VirtualHost *:83>    DocumentRoot "E:\PhpProjects"</VirtualHost><Directory ...

  4. vs 2013调试的时候重启的解决方案

    今天在用vs 2013 调试程序的时候,vs 总是莫名其妙的关闭,停止运行,泪蹦了..... 是什么原因呢? 以前的时候可是好好的啊,经过认真的思索,最近装过和vs 2013 相关的程序也只有 ref ...

  5. 最受欢迎linux命令

    1.   以 root 帐户执行上一条命令 sudo !! 2.  利用 Python 搭建一个简单的 Web 服务器,可通过 http://$HOSTNAME:8000访问    python -m ...

  6. linux下的type命令

    type命令用来显示指定命令的类型.一个命令的类型可以是如下几种: alias 别名 keyword 关键字,Shell保留字 function 函数,Shell函数 builtin 内建命令,She ...

  7. jquery cycle pugin

    插件地址: http://jquery.malsup.com/cycle/ <div id="propaganda"><div id="pgdImg&q ...

  8. 告诉你LTE-FDD与LTE-TDD的区别

    [PConline 技术分析]移动早在去年已经拿下了TD-LTE的4G牌照,而中国联通与中国电信的FDD-LTE的牌照在近日正式拿下,而对于4G网络,有多少真正了解呢?接下来笔者就为大家解释一下4G的 ...

  9. 混血儿爹妈要混的远,数据库与WEB分离,得混的近

    最近搞了个漫画网站,放在香港VPS,由于内存不够,把数据库移到了阿里云,混的远了点,没缓存的时候网站打开速度慢了1秒左右.笨狗漫画:http://www.bengou8.com 底部有sql时间cop ...

  10. COM编程入门第一部分——什么是COM,如何使用COM

    本文的目的是为刚刚接触COM的程序员提供编程指南,并帮助他们理解COM的基本概念.内容包括COM规范简介,重要的COM术语以及如何重用现有的COM组件.本文不包括如何编写自己的COM对象和接口. CO ...