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枚舉器來實現遍歷功能。