购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

4.6 枚举类型和迭代器

4.6.1 枚举类型

枚举器(enumerator)是一个只读的且只能在值序列上前移的游标。在C#中,如果类型满足以下规则中的一种,那么该类型就可以作为枚举器使用:

· 拥有名为 MoveNext 的public无参方法,并拥有一个名为 Current 的public属性。

· 实现了 System.Collections.Generic.IEnumerator<T> 接口。

· 实现了 System.Collections.IEnumerator 接口。

foreach 语句用来在可枚举(enumerable)对象上执行迭代操作。可枚举对象是序列的逻辑表示,它本身不是游标,但是它可以在对象自身上生成游标。在C#中,如果类型满足以下规则中的一种,那么该类型就是一个可枚举类型(以下规则的检查是按顺序执行的):

· 拥有名为 GetEnumerator 的public无参方法,且该方法返回枚举器。

· 实现了 System.Collections.Generic.IEnumerable<T> 接口。

· 实现了 System.Collections.IEnumerable 接口。

· (从C# 9之后)可以绑定到名为 GetEnumerator 的扩展方法,并且该扩展方法返回枚举器(请参见4.9节)。

枚举类型模式如下:

以下是遍历单词beer中每一个字母的高级方法:

以下程序则是使用低层的调用,即不用 foreach 语句遍历单词beer的每一个字母的方法:

如果迭代器实现了 IDisposable ,则 foreach 语句也会起到 using 语句的作用,隐式销毁枚举器对象。

我们将在第7章详细介绍枚举相关的接口。

4.6.2 集合的初始化器

只需一个简单的步骤就可以实例化并填充可枚举对象:

编译器会将上述语句转换为:

它要求可枚举对象实现 System.Collections.IEnumerable 接口,并且有可以调用的带适当数量参数的 Add 方法。同样我们也能够使用类似的方式初始化字典(请参见7.5节):

或者更加简洁地写为:

第二种写法不仅适用于字典而且适用于任何具有索引器的类型。

4.6.3 迭代器

foreach 语句是枚举器的消费者,而迭代器是枚举器的生产者。在本例中,我们使用迭代器来返回斐波那契数列(斐波那契数列表中的每一个数字是前两个数字之和):

return 语句表示“这是该方法的返回值”,而 yield return 语句则表示“这是当前枚举器产生的下一个元素”。在每条 yield 语句中,控制都返回给调用者,但是必须同时维护调用者的状态,以便调用者枚举下一个元素的时候,能够继续执行该方法。该状态的生命周期是与枚举器绑定的。在调用者枚举结束之后,该状态就可以被释放。

编译器将迭代方法转换为实现了 IEnumerable<T> IEnumerator<T> 的私有类。迭代器块中的逻辑被“反转”并分别进入编译器生成的枚举器类的 MoveNext 方法和 Current 属性。当调用迭代器方法的时候,所做的仅仅是实例化编译器生成的类,而迭代器代码并没有真正执行。编写的迭代器代码只有当开始枚举结果序列时才开始执行,通常使用 foreach 语句。

迭代器可以是局部方法(请参见3.1.3.2节)。

4.6.4 迭代器语义

迭代器是包含一个或者多个 yield 语句的方法、属性或者索引器。迭代器必须返回以下四个接口之一(否则编译器会产生相应错误):

迭代器具有不同的语义,这取决于迭代器返回的是可枚举接口还是枚举器接口,我们将在第7章说明。

我们可以一次使用多个 yield 语句,例如:

4.6.4.1 yield break语句

return 语句在迭代器块中是非法的。如果希望提前退出迭代器块,应该使用 yield break 语句。我们可以将 Foo 修改为如下示例:

4.6.4.2 迭代器和try/catch/finally语句块

yield return 语句不能出现在带有 catch 子句的 try 语句块中:

yield return 语句也不能出现在 catch 或者 finally 语句块中。出现这些限制的原因是编译器必须将迭代器转换为带有 MoveNext Current Dispose 成员的普通类,而转换异常处理语句块会大大增加代码的复杂性。

但是可以在只带有 finally 语句块的 try 语句块中使用yield语句:

当枚举器到达序列末尾或被销毁时就可以执行 finally 语句块了。如果枚举提前结束,则 foreach 语句会隐式销毁枚举器,这是消费枚举器的安全且正确的做法。当显式使用枚举器时,一个陷阱是提前结束枚举而不销毁枚举器,从而绕过 finally 语句块的执行。我们可以将枚举器的使用显式包裹在 using 语句中来避免上述错误。

4.6.5 组合序列

迭代器的可组合性高。我们可以扩展前面的示例,只输出偶数斐波那契数列:

每个元素只有到最后关头,即执行 MoveNext() 操作时才会进行计算,图4-1显示了随时间变化的数据请求和数据输出。

图4-1:组合序列

迭代器模式的可组合性对LINQ而言非常重要,我们将在第8章进行讨论。 kUB2ACDV4HOcjjIQ1I9Ql+fI4hXk/JlwyTUKt2ChkAZFCqOJ3TWstYV5hzeSXkzH

点击中间区域
呼出菜单
上一章
目录
下一章
×