2021年了,`IEnumerator`、`IEnumerable`還傻傻分不清楚?

IEnumeratorIEnumerable這兩個接口單詞相近、含義相關,傻傻分不清楚。
入行多年,一直沒有系統性梳理這對李逵李鬼。

最近本人在懟着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,你就不能將IEnumerableGetEnumerator方法的返回值轉換爲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訪問器
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章