IEnumerable
饮水思源
《C#本质论》
Overview
根据定义,.Net 的中集合,本质上是一个类,它最起码实现了IEnumeraable 或者非泛型的IEnumerable 结构。 这个接口非常关键,如果想要支持对集合执行的遍历操作,子起码的要求就是实现由IEnumerable 规定的方法。
foreach 和 数组
看下面的代码
string[] strArr = { "1", "2", "3" }; foreach (var item in strArr) { Console.WriteLine(item); }
上面的代码想必已经非常的熟悉了,我们来看一下上面的代码究竟是什么样的
string[] array = new string[] { "1", "2", "3" }; string[] array2 = array; for (int i = 0; i < array2.Length; i++) { string value = array2[i]; Console.WriteLine(value); }
在上面的代码中,奖foreach编译为了for循环,这就需要依赖于数组的Length 属性来确定for循环的次数了。
foreach 和 IEnumerable
因为数据的长度是固定的,并且可以使用索引操作符,这样可以通过for循环的形式来达到遍历数组的目的,但是对IEnumerable 接口的集合,可能就不太适用了,比如说: stack<T>
Queue<T>
等等,是不支持按照索引获取元素的,所以要有一个更加普适的方式来达到遍历的目的,这种方式就是迭代器 (iterator) 模式
, 它只需要确定,集合的第一个元素,下一个元素,和最后一个元素就能达到遍历的目的。
System.Collections.Generic.IEnumerator 和 System.Collections.IEnumerator 接口,分别实现了,泛型和非泛型的迭代器模式,可以使用迭代器模式遍历集合,而不是通过索引操作符来遍历。
继承层次类图
其中,IEnumerator 中三个成员, 只读属性object Current
代表当前的元素,方法bool MoveNext()
将元素移动到下一个元素,同时检测是否枚举完成了集合中的每一个元素。 void Reset()
方法永远不要主动调用他 , 该方法会抛出一个 NotImplemented Exception
异常,IEnumerator 重载了Current属性,提供了泛型的实现。
使用迭代器遍历集合
static void Main(string[] args) { Stack<string> stack = new Stack<string>(); stack.Push("1"); stack.Push("2"); stack.Push("3"); stack.Push("4"); stack.Push("5"); IEnumerator<string> enumerator = stack.GetEnumerator(); //使用迭代器遍历集合 while (enumerator.MoveNext()) { string value = enumerator.Current; Console.WriteLine(value); } }
当然使用While循环遍历的太麻烦了
static void Main(string[] args) { Stack<string> stack = new Stack<string>(); stack.Push("1"); stack.Push("2"); stack.Push("3"); stack.Push("4"); stack.Push("5"); foreach (var item in stack) { Console.WriteLine(item); } }
foreach遍历反编译的IL代码
.try { IL_004b: br.s IL_005e // loop start (head: IL_005e) IL_004d: ldloca.s 1 IL_004f: call instance !0 valuetype [System]System.Collections.Generic.Stack`1/Enumerator<string>::get_Current()//获取Current只读属性 IL_0054: stloc.2 IL_0055: nop IL_0056: ldloc.2 IL_0057: call void [mscorlib]System.Console::WriteLine(string)//输出 IL_005c: nop IL_005d: nop IL_005e: ldloca.s 1 IL_0060: call instance bool valuetype [System]System.Collections.Generic.Stack`1/Enumerator<string>::MoveNext()//调用MoveNext方法 IL_0065: brtrue.s IL_004d // end loop IL_0067: leave.s IL_0078 } // end .try //回收资源 finally { IL_0069: ldloca.s 1 IL_006b: constrained. valuetype [System]System.Collections.Generic.Stack`1/Enumerator<string> IL_0071: callvirt instance void [mscorlib]System.IDisposable::Dispose() IL_0076: nop IL_0077: endfinally } // end handler
最终的IL代码还是会编译成类似While循环的方式,foreach循环只不是帮助我们减少了不必要的代码量。
状态共享
请思考这么一个问题,上述的代码,如果循环的时候,再次嵌套一个循环再次访问stack 集合,那么,MoveNext方法会不会乱套呢?
答案是不会的? 请注意,在上面的类图中我们的集合类是没有直接的继承自IEnumerator<T>
接口的,而是继承了IEnumerable 接口,这个接口仅仅提供了一个方法,那就是获取IEnumerator<T>
对象,MoveNext() 方法的状态,不是有集合类来维持的,而是由类外一个类来维持状态的,这个类通常是一个内部类,以方便访问集合的成员。
清理状态
由于由实现了IEnumerator<T>
接口的类来维护状态,有时候在遍历完集合或者引发的异常的时候有时候需要清理一下状态,为了这种考虑,IEnumerator
集合还实现了,
<wiz_tmp_tag id="wiz-table-range-border" contenteditable="false" style="display: none;">