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