IEnumerator和IEnumerable详解
IEnumerator和IEnumerable
从名字常来看,IEnumerator是枚举器的意思,IEnumerable是可枚举的意思。
了解了两个接口代表的含义后,接着看源码:
IEnumerator:
public interface IEnumerator { // Interfaces are not serializable // Advances the enumerator to the next element of the enumeration and // returns a boolean indicating whether an element is available. Upon // creation, an enumerator is conceptually positioned before the first // element of the enumeration, and the first call to MoveNext // brings the first element of the enumeration into view. // bool MoveNext(); // Returns the current element of the enumeration. The returned value is // undefined before the first call to MoveNext and following a // call to MoveNext that returned false. Multiple calls to // GetCurrent with no intervening calls to MoveNext // will return the same object. // Object Current { get; } // Resets the enumerator to the beginning of the enumeration, starting over. // The preferred behavior for Reset is to return the exact same enumeration. // This means if you modify the underlying collection then call Reset, your // IEnumerator will be invalid, just as it would have been if you had called // MoveNext or Current. // void Reset(); }
IEnumerable:
public interface IEnumerable { // Interfaces are not serializable // Returns an IEnumerator for this enumerable Object. The enumerator provides // a simple way to access all the contents of a collection. [Pure] [DispId(-4)] IEnumerator GetEnumerator(); }
发现IEnumerable只有一个GetEnumerator函数,返回值是IEnumerator类型,从注释我们可以得知IEnumerable代表继承此接口的类可以获取一个IEnumerator来实现枚举这个类中包含的集合中的元素的功能(比如List<T>,ArrayList,Dictionary等继承了IEnumeratble接口的类)。
用foreach来了解IEnumerable,IEnumerator的工作原理
我们模仿ArrayList来实现一个简单的ConstArrayList,然后用foreach遍历。
//一个常量的数组,用于foreach遍历 class ConstArrayList : IEnumerable { public int[] constItems = new int[] { 1, 2, 3, 4, 5 }; public IEnumerator GetEnumerator() { return new ConstArrayListEnumeratorSimple(this); } } //这个常量数组的迭代器 class ConstArrayListEnumeratorSimple : IEnumerator { ConstArrayList list; int index; int currentElement; public ConstArrayListEnumeratorSimple(ConstArrayList _list) { list = _list; index = -1; } public object Current { get { return currentElement; } } public bool MoveNext() { if(index < list.constItems.Length - 1) { currentElement = list.constItems[++index]; return true; } else { currentElement = -1; return false; } } public void Reset() { index = -1; } } class Program { static void Main(string[] args) { ConstArrayList constArrayList = new ConstArrayList(); foreach(int item in constArrayList) { WriteLine(item); } ReadKey(); } }
输出结果:
1
2
3
4
5
代码达到了遍历效果,但是在用foreach遍历时,IEnumerator和IEnumerable究竟是如何运行的,我们可以通过增加增加日志可以直观的看到原因。
//一个常量的数组,用于foreach遍历 class ConstArrayList : IEnumerable { public int[] constItems = new int[] { 1, 2, 3, 4, 5 }; public IEnumerator GetEnumerator() { WriteLine("GetIEnumerator"); return new ConstArrayListEnumeratorSimple(this); } } //这个常量数组的迭代器 class ConstArrayListEnumeratorSimple : IEnumerator { ConstArrayList list; int index; int currentElement; public ConstArrayListEnumeratorSimple(ConstArrayList _list) { list = _list; index = -1; } public object Current { get { WriteLine("Current"); return currentElement; } } public bool MoveNext() { if(index < list.constItems.Length - 1) { WriteLine("MoveNext true"); currentElement = list.constItems[++index]; return true; } else { WriteLine("MoveNext false"); currentElement = -1; return false; } } public void Reset() { WriteLine("Reset"); index = -1; } } class Program { static void Main(string[] args) { ConstArrayList constArrayList = new ConstArrayList(); foreach(int item in constArrayList) { WriteLine(item); } ReadKey(); } }
输出结果:
GetIEnumerator
MoveNext true
Current
1
MoveNext true
Current
2
MoveNext true
Current
3
MoveNext true
Current
4
MoveNext true
Current
5
MoveNext false
通过输出结果,我们可以发现,foreach在运行时会先调用ConstArrayList的GetIEnumerator函数获取一个ConstArrayListEnumeratorSimple,之后通过循环调用ConstArrayListEnumeratorSimple的MoveNext函数,index后移,更新Current属性,然后返回Current属性,直到MoveNext返回false。
总结一下:
GetIEnumerator()负责获取枚举器。
MoveNext()负责让Current获取下一个值,并判断遍历是否结束。
Current负责返回当前指向的值。
Rest()负责重置枚举器的状态(在foreach中没有用到)
这些就是IEnumerable,IEnumerator的基本工作原理了。
其次我们发现:
ConstArrayList constArrayList = new ConstArrayList(); foreach(int item in constArrayList) { writeLine(item); }
其实就等价于:
ConstArrayList constArrayList = new ConstArrayList(); IEnumerator enumeratorSimple = constArrayList.GetEnumerator(); while (enumeratorSimple.MoveNext()) { int item = (int)enumeratorSimple.Current; WriteLine(item); }
也就是说foreach其实是一种语法糖,用来简化对可枚举元素的遍历代码。而被遍历的类通过实现IEnumerable接口和一个相关的IEnumerator枚举器来实现遍历功能。
上一篇: 爱国的诗歌