匹夫细说C#:庖丁解牛迭代器,那些藏在幕后的秘密

时间:2022-05-12 05:17:38

0x00 前言

在匹夫的上一篇文章《匹夫细说C#:不是“栈类型”的值类型,从生命周期聊存储位置》的最后,匹夫以总结和后记的方式涉及到一部分迭代器的知识。但是觉得还是不够过瘾,很多需要说清楚的内容还是含糊不清,所以这周就专门写一下c#中的迭代器吧。

0x01 你好,迭代器

首先思考一下,在什么情景下我们需要使用到迭代器?

假设我们有一个数据容器(可能是Array,List,Tree等等),对我们这些使用者来说,我们显然希望这个数据容器能提供一种无需了解它的内部实现就可以获取其元素的方法,无论它是Array还是List或者别的什么,我们希望可以通过相同的方法达到我们的目的。

此时,迭代器模式(iterator pattern)便应运而生,它通过持有迭代状态,追踪当前元素并且识别下一个需要被迭代的元素,从而可以让使用者透过特定的界面巡访容器中的每一个元素而不用了解底层的实现。

那么,在c#中,迭代器到底是以一个怎样的面目出现的呢?

如我们所知,它们被封装在IEnumerable和IEnumerator这两个接口中(当然,还有它们的泛型形式,要注意的是泛型形式显然是强类型的。且IEnumerator<T>实现了IDisposable接口)。

IEnumerable非泛型形式:

//IEnumerable非泛型形式[ComVisibleAttribute(True)] [GuidAttribute("496B0ABE-CDEE-11d3-88E8-00902754C43A")] public interface IEnumerable { IEnumerator GetEnumerator(); }

IEnumerator非泛型形式:

//IEnumerator非泛型形式 [ComVisibleAttribute(true)] [GuidAttribute("496B0ABF-CDEE-11d3-88E8-00902754C43A")] public interface IEnumerator { Object Current {get;} bool MoveNext(); void Reset(); }

IEnumerable泛型形式:

//IEnumerable泛型形式public interface IEnumerable<out T> : IEnumerable { IEnumerator<T> GetEnumerator(); IEnumerator GetEnumerator(); }

IEnumerator泛型形式:

//IEnumerator泛型形式public interface IEnumerator<out T> : IDisposable, IEnumerator { void Dispose(); Object Current {get;} T Current {get;} bool MoveNext(); void Reset(); } [ComVisibleAttribute(true)] public interface IDisposable { void Dispose(); }

IEnumerable接口定义了一个可以获取IEnumerator的方法——GetEnumerator()。

而IEnumerator则在目标序列上实现循环迭代(使用MoveNext()方法,以及Current属性来实现),直到你不再需要任何数据或者没有数据可以被返回。使用这个接口,可以保证我们能够实现常见的foreach循环。

为什么会有2个接口?

到此,各位看官是否和曾经的匹夫有相同的疑惑呢?那就是为何IEnumerable自己不直接实现MoveNext()方法、提供Current属性呢?为何还需要额外的一个接口IEnumerator来专门做这个工作?

OK,假设有两个不同的迭代器要对同一个序列进行迭代。当然,这种情况很常见,比如我们使用两个嵌套的foreach语句。我们自然希望两者相安无事,不要互相影响彼此。所以自然而然的,我们需要保证这两个独立的迭代状态能够被正确的保存、处理。这也正是IEnumerator要做的工作。而为了不违背单一职责原则,不使IEnumerable拥有过多职责从而陷入分工不明的窘境,所以IEnumerable自己并没有实现MoveNext()方法。

迭代器的执行步骤

为了更直观的了解一个迭代器,匹夫这里提供一个小例子。