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 |
