许久以前学习WinForm的时候开始解除的C#,以后便搁了好长时间。最近在学Unity的时候又重拾了C#。发现之前学过的东西也都忘了差很少了。特别在Unity中会常常用到yield
关键字和IEnumerator
接口来作一些延时循环的操做。做为重拾C#第一步,先来复习和总结一下C#中的IEnumerable
和IEnumerator
接口。javascript
在不少编程场景中,咱们须要去遍历(Iterate)一个集合。为了让这个过程简化,不少高级语言都采用遍历语句来进行操做,例如for...in...
或者foreach()
。例如在C#中,ArrayList的实例能够在foreach()中获得遍历。固然除了语言原生的一些集合类外不是全部的类的实例均可以放在遍历语句中进行操做的。要想让一个类的实例可以在遍历语句中获得遍历,就必须按照语言的规定让类实现某些接口或者属性。java
在咱们开始正式进入正题前,咱们先看一下C#原生的ArrayList是怎么样工做的。express
using System.IO; using System; using System.Collections; class Test { static void Main() { ArrayList array = new ArrayList(); array.Add(1); array.Add(2); array.Add("3"); array.Add(4); foreach(object i in array){ Console.WriteLine(i); } } }
很简单是吧,那咱们下面就开始阐述IEnumerable
和IEnumerator
接口。并实现一个本身版本的ArrayList
。编程
首先咱们来认识一下咱们今天的主角,IEnumerable
, IEnumerable<T>
, IEnumerator
, IEnumberator<T>
,长的真的很像呢。首先咱们一眼就能够看出后面带的就是有类型的(C#里叫作generic
)。generic版本和non-generic版本稍有些不一样。做为开始咱们先实现non-generic版吧。那就选定了咱们的男一和女一,IEnumerable
, IEnumerator
。瞅一眼它们的简历吧。官方文档是这么写的:ide
IEnumerable is the base interface for all non-generic collections that can be enumerated. For the generic version of this interface see System.Collections.Generic.IEnumerable. IEnumerable contains a single method, GetEnumerator, which returns an IEnumerator. IEnumerator provides the ability to iterate through the collection by exposing a Current property and MoveNext and Reset methods.函数
IEnumerable
是那些能够被遍历的集合中所须要实现的基础接口,IEnumerable
有一个方法GetEnumerator()
,这个方法发回一个IEnumerator
类型,IEnumerator
包含一个Current
属性和MoveNext
和Reset
方法,经过这些属性和方法就能够遍历这个集合了。因此咱们本身的ArrayList应该这么实现:学习
public class MyArrayList: IEnumerable { //some code public IEnumerator GetEnumerator() { //some code //return new MyEnumerator(...); } }
能够看出GetEnumerator返回一个IEnumerator类型,因此咱们就必需要去实现本身的IEnumerator类:this
public class MyEnumerator:IEnumerator { public bool MoveNext() { //some code } public void Reset() { //some code } public object Current { get { // some code } } }
知道了基本结构,就能够扩展出咱们想要的ArrayList结构了,完整的代码以下:code
using System.IO; using System; using System.Collections; public class MyArrayList { object[] data; int currentIndex; public MyArrayList(int length) { this.data = new object[length]; currentIndex = 0; } public void Add(object s) { data[currentIndex++] = s; } public IEnumerator GetEnumerator() { return new MyEnumerator(data); } } public class MyEnumerator:IEnumerator { private object[] _data; private int position = -1; public MyEnumerator(object[] data) { _data = data; } public bool MoveNext() { position++; return (position < _data.Length); } public void Reset() { position = -1; } object IEnumerator.Current { get { return Current; } } public object Current { get { return _data[position]; } } } class Test { static void Main() { MyArrayList array = new MyArrayList(10); array.Add("Jack"); array.Add("Tom"); foreach(object i in array) { Console.WriteLine(i); } } }
这样一个简单的ArrayList就实现了。还有一点要注意的就是IEnumerable
接口不是必需要实现的,可是要想能遍历,必需要实现GetEnumerator()
方法。可是实现IEnumerable
和IEnumberator
接口是个好习惯。orm
对于IEnumerable<T>
和IEnumerator<T>
的实现,稍有些不一样,若是咱们只是把上面代码中的IEnumerable
和IEnumerator
换成对应的Generic接口的话:
public class MyArrayList<T>: IEnumerable<T> { //some code public IEnumerator<T> GetEnumerator() { //some code //return new MyEnumerator<T>(...); } } public class MyEnumerator<T>:IEnumerator<T> { public bool MoveNext() { //some code } public void Reset() { //some code } public T Current { get { // some code } } }
这样编译器会报三个错误:
1.
MyEnumerator<T>
does not implement interface memberSystem.IDisposable.Dispose()
2.MyArrayList<T>
does not implement interface memberSystem.Collections.IEnumerable.GetEnumerator()
and the best implementing candidateMyArrayList<T>.GetEnumerator()
return typeSystem.Collections.Generic.IEnumerator<T>
does not match interface member return typeSystem.Collections.IEnumerator
3.MyEnumerator<T>
does not implement interface memberSystem.Collections.IEnumerator.Current.get
and the best implement ing candidateMyEnumerator<T>.Current.get
return typeT
does not match interface member return typeobject
第一个错误告诉咱们IEnumerable<T>
要实现Dispose()
方法,第二个,第三个错误要咱们实现IEnumerable.GetEnumerator()
和IEnumerator.Current
属性。完整代码以下:
using System.IO; using System; using System.Collections; using System.Collections.Generic; public class MyArrayList<T>: IEnumerable<T> { T[] data; int currentIndex; public MyArrayList(int length) { this.data = new T[length]; currentIndex = 0; } public void Add(T s) { data[currentIndex++] = s; } IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); } public IEnumerator<T> GetEnumerator() { return new MyEnumerator<T>(data); } } public class MyEnumerator<T>:IEnumerator<T> { private T[] _data; private int position = -1; public MyEnumerator(T[] data) { _data = data; } public bool MoveNext() { position++; return (position < _data.Length); } public void Reset() { position = -1; } public void Dispose() { //Dispose the resource } object IEnumerator.Current { get { return Current; } } public T Current { get { return _data[position]; } } } public class Test { static void Main() { MyArrayList<string> array = new MyArrayList<string>(10); array.Add("Jack"); array.Add("Tom"); foreach(string str in array) { Console.WriteLine(str); } } }
用上面的方法来实现一个能够遍历的类多少以为有些麻烦,要多实现一个IEnumerator
类。并且Generic版本要多实现几个方法和属性。yield
关键字能够帮咱们简化上述的过程。yield
用于生成一个遍历类型,包含yield
的方法的返回值类型必须是IEnumerable
,IEnumerator
,IEnumerable<T>
,IEnumerator<T>
其中一种。yield
的通常形式是yield return <expression>
。例如:
public class PowersOf2 { static void Main() { // Display powers of 2 up to the exponent of 8: foreach (int i in Power(2, 8)) { Console.Write("{0} ", i); } } public static System.Collections.Generic.IEnumerable<int> Power(int number, int exponent) { int result = 1; for (int i = 0; i < exponent; i++) { result = result * number; yield return result; } } // Output: 2 4 8 16 32 64 128 256 }
调用上面的Power
方法,不会执行函数的主体,而是返回一个IEnumerable<int>
对象,在foreach
中,调用MoveNext
来进行遍历,这时函数开始执行,指导碰到yield
,后面返回的对象就是Current属性的值。下次调用MoveNext的时候,会从上个yield的地方继续日后执行。这样知道函数结束,遍历也就结束了。知道这个特性后咱们就能简化上面的代码了:
using System.IO; using System; using System.Collections; using System.Collections.Generic; public class MyArrayList<T>: IEnumerable<T> { T[] data; int currentIndex; public MyArrayList(int length) { this.data = new T[length]; currentIndex = 0; } public void Add(T s) { data[currentIndex++] = s; } IEnumerator IEnumerable.GetEnumerator(){ return this.GetEnumerator(); } public IEnumerator<T> GetEnumerator() { for(int i = 0; i < data.Length; i++) { yield return data[i]; } } } public class Test { static void Main() { MyArrayList<string> array = new MyArrayList<string>(10); array.Add("Jack"); array.Add("Tom"); foreach(string str in array) { Console.WriteLine(str); } } }
对于遍历的实现,每一个语言都有本身不一样的实现,但却有很大的类似处,了解一个语言的实现也有助于对其余语言实现的理解。结下来会写一下javascript中对于遍历的实现。