本文转载自微信公众号「全栈码农画像」,作者小码甲 。转载本文请联系全栈码农画像公众号。
IEnumerator、IEnumerable这两个接口单词相近、含义相关,傻傻分不清楚。
入行多年,一直没有系统性梳理这对李逵李鬼。
最近本人在怼着why神的《其实吧,LRU也就那么回事》,方案1使用数组实现LRU,手写算法涉及这一对接口,借此时机覆盖这一对难缠的冤家。
IEnumerator
IEnumerator、IEnumerable接口有相似的名称,这两个接口通常也在一起使用,它们有不同的用途。
IEnumerator接口为类内部的集合提供了迭代方式, IEnumerator 要求你实现三个方法:
- MoveNext方法:该方法将集合索引加1,并返回一个bool值,指示是否已到达集合的末尾。
- Reset方法:它将集合索引重置为其初始值-1,这会使枚举数无效。
- Current方法: 返回position位置的当前对象
IEnumerable
IEnumerable接口为foreach迭代提供了支持,IEnumerable要求你实现GetEnumerator方法。
- public IEnumerator GetEnumerator()
- {
- return (IEnumerator)this;
- }
该用哪一个接口?
仅凭以上辞藻,很难区分两个接口的使用场景。
IEnumerator接口定义对类中的集合类型对象的迭代方式,
IEnumerable接口允许使用foreach循环进行枚举。
因此IEnumerable接口的GetEnumerator方法会返回一个IEnumerator接口。要实现IEnumerable,你还必须实现IEnumerator。
从英文词根上讲:
IEnumerator接口代表了枚举器,里面定义了枚举方式,是名词。
IEnumerable接口代表该对象具备了可被枚举的性质,是形容词。
总之,如果您想提供对foreach的支持,那么就先让对象可枚举,再谈论枚举方式,也就是说实现这两个接口。
最佳实践
- 在嵌套类中实现IEnumerator,这样你可以创建多个枚举器。
- 为IEnumerator的Current方法提供异常处理。
为什么要这么做?
如果集合的内容发生变化,则reset方法将被调用,紧接着当前枚举数无效,您将收到一个IndexOutOfRangeException异常(其他情况也可能导致此异常)。所以执行一个Try…Catch块来捕获这个异常并引发InvalidOperationException异常, 提示在迭代时不允许修改集合内容。
“这也正是我们常见的在foreach 里面尝试修改迭代对象会报InvalidOperationException异常的原因。
下面以汽车列表为例实现IEnumerator IEnumerable接口
- using System;
- using System.Collections;
- namespace ConsoleEnum
- {
- public class cars : IEnumerable
- {
- private car[] carlist;
- //Create internal array in constructor.
- public cars()
- {
- carlist= new car[6]
- {
- new car("Ford",1992),
- new car("Fiat",1988),
- new car("Buick",1932),
- new car("Ford",1932),
- new car("Dodge",1999),
- new car("Honda",1977)
- };
- }
- //private enumerator class
- private class MyEnumerator:IEnumerator
- {
- public car[] carlist;
- int position = -1;
- //constructor
- public MyEnumerator(car[] list)
- {
- carlist=list;
- }
- private IEnumerator getEnumerator()
- {
- return (IEnumerator)this;
- }
- //IEnumerator
- public bool MoveNext()
- {
- position++;
- return (position < carlist.Length);
- }
- //IEnumerator
- public void Reset()
- {
- position = -1;
- }
- //IEnumerator
- public object Current
- {
- get
- {
- try
- {
- return carlist[position];
- }
- catch (IndexOutOfRangeException)
- {
- throw new InvalidOperationException();
- }
- }
- }
- } //end nested class
- public IEnumerator GetEnumerator()
- {
- return new MyEnumerator(carlist);
- }
- }
- }
在foreach cars的时候,可以明显看到
- foreach语法糖初次接触可枚举的cars, 实际会访问cars实现的 GetEnumerator()方法,拿到迭代器
- foreach每次迭代,实际会访问迭代器的Current属性