CS4L22——迭代器
CS4L22——迭代器
本章代码关键字
1 | IEnumerable // 可迭代接口,需要实现返回迭代器IEnumerator的方法 |
迭代器
迭代器(iterator
)有时又称光标(cursor
),是程序设计的软件设计模式,
迭代器模式提供一个方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的标识
从表现结果上看,迭代器是可以在容器对象(例如链表或者数组)上遍历访问的接口,设计人员无需关心容器对象的内存分配的实现细节
可以用 foreach
遍历的类,都是实现了迭代器的接口
标准迭代器的实现方法
- 关键接口:
IEnumerator
,IEnumerable
- 命名空间:
using System.Collections;
迭代器的一个最常用的用途就是 foreach
遍历,首先拆解 foreach
遍历执行的内容
foreach 的本质
拆解 foreach
中具体每一步做的事情:
-
首先会调用
in
后面这个对象的GetEnumerator()
方法,来获取IEnumerator
对象 -
执行得到的这个
IEnumerator
对象中的MoveNext()
方法其中,前两个步骤只会执行一次!接下来就是反复循环三四步,直到第三步的
MoveNext()
返回false
-
只要
MoveNext()
方法的返回值是true
,就会去从IEnumerator
的Current
属性取值,然后赋值给in
前面的变量,执行foreach
语句块 -
执行
foreach
语句块执行完毕,回到第三步,直到MoveNext()
方法返回false
,foreach
结束
因此,如果要使自定义类可以使用 foreach
遍历,
则必须要实现一个 GetEnumerator
方法,也就是 IEnumerable
内声明的方法,返回的 IEnumerator
对象就是被迭代的主体
而被迭代的,实现 IEnumerator
的对象,需要在内部存储一个光标(cursor
),类似于当前迭代到的对象,索引之类,
然后,在 MoveNext()
方法中,需要将这个光标向后移动,让光标指向下一个对象,根据对象存在与否返回 bool
值,
如果光标移动后可以指向一个对象,就返回 true
,告诉外部可以通过 Current
取值,如果光标越界或溢出,就返回 false
,告诉外部迭代结束,
而 Reset()
方法则需要将光标移动到最开始的位置上,同时,此方法一般是在内部调用,因此,可以在 GetEnumerator()
方法处调用
一个类可以通过同时继承 IEnumerator
和 IEnumerable
并实现其中的方法,也可以只继承 IEnumerable
,然后返回另一个 IEnumerator
对象
假设自定义类同时继承 IEnumerator
和 IEnumerable
并实现其中的方法,让 foreach
可以迭代循环
1 | using System.Collections; |
这样,foreach
就可以正常迭代了
1 | CustomList list = new CustomList(); |
输出:
1 | 1 |
用 yield return 语法糖实现迭代器
yield return
是 C# 提供给我们的语法糖,它可以用于返回值为 IEnumerator
的函数内
所谓语法糖,也称糖衣语法,主要作用就是将复杂逻辑简单化,可以增加程序的可读性,从而减少程序代码出错的机会
yield
关键字是配合迭代器使用,可以理解为暂时返回,保留当前的状态,
下次调用从 yield return
的位置继续,原理其实和上面的标准迭代器一样,但是 C# 帮我们自动写好了代码
- 关键接口:
IEnumerable
- 命名空间:
using System.Collections;
让想要通过 foreach
遍历的自定义类实现接口中的方法 GetEnumerator
即可
1 | using System.Collections; |
yield
关键字背后的原理和之前实现的标准迭代器一样,只是 C# 编译器后续会根据这个关键字自动生成那些标准迭代器语句
1 | YieldTest list = new YieldTest(); |
输出:
1 | 1 |
用 yield return 语法糖为泛型类实现迭代器
泛型类也可以直接使用 IEnumerable
以支持迭代器
1 | class YieldTest<T> : IEnumerable |
1 | YieldTest<string> list = new YieldTest<string>(new string[] { "aaa", "bbb", "ccc", "ddd", "eee" }); |
输出:
1 | aaa |
IEnumerable
也有泛型接口:IEnumerable<T>
,它可以限制迭代器迭代的类型,同时也可以限制 foreach
循环时的变量类型,避免类型出错,
由于 IEnumerable<T>
继承了 IEnumerable
,因此实现 IEnumerator<T> GetEnumerator()
方法的同时,
还需要实现 IEnumerator GetEnumerator()
,但因为和泛型函数同名了,因此需要显式实现接口: IEnumerator IEnumerable.GetEnumerator()
这样,除非将 IEnumerable<T>
对象得到的 IEnumerator<T>
对象转换为 IEnumerator
对象,否则迭代器迭代的类型都是由 T
决定的:
1 | class YieldTest<T> : IEnumerable<T> |
1 | YieldTest<string> list = new YieldTest<string>(new string[] { "aaa", "bbb", "ccc", "ddd", "eee" }); |
输出:
1 | aaa |