IEnumerator、IEnumerable傻傻分不清楚?
IEnumerator
、IEnumerable
这两个接口单词相近、含义相关,傻傻分不清楚。
入行多年,一直没有系统性梳理这对李逵李鬼。
最近本人在怼着why神的《其实吧,LRU也就那么回事》,方案1使用数组实现LUR,手写算法涉及这一对接口,借此机会本次覆盖这一对难缠的冤家。
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
的GetEnumerator
方法的返回值转换为IEnumerator
接口。
从英文词根上讲:
IEnumerator接口代表了枚举器,里面定义了枚举方式;
IEnumerable接口代表该对象具备了可被枚举的性质。
总之,IEnumerable的使用要求类实现IEnumerator。如果您想提供对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属性的get访问器