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訪問器