2021年了,`IEnumerator`、`IEnumerable`接口還傻傻分不清楚?
本文轉載自微信公眾號「全棧碼農畫像」,作者小碼甲 。轉載本文請聯系全棧碼農畫像公眾號。
IEnumerator、IEnumerable這兩個接口單詞相近、含義相關,傻傻分不清楚。
入行多年,一直沒有系統性梳理這對李逵李鬼。
最近本人在懟著why神的《其實吧,LRU也就那么回事》,方案1使用數組實現LRU,手寫算法涉及這一對接口,借此時機覆蓋這一對難纏的冤家。
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接口代表該對象具備了可被枚舉的性質,是形容詞。
總之,如果您想提供對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屬性