C# 枚舉器(enumerator)


總結:

1、枚舉器就像是序列中的“游標”或“書簽”。可以有多個“書簽”,移動其中任何一個都可以枚舉集合,與其他枚舉器互不影響。用來遍歷數據結構(單項表鏈、數組、集合類成員等)。

2、可以使用foreach 遍歷枚舉器。foreach 用來遍歷鴨子類型.點擊查看foreach詳細用法

什么是枚舉器

實現IEnumerator接口的類就是枚舉器。

枚舉器作用

1、枚舉器就像是序列中的“游標”或“書簽”。可以有多個“書簽”,移動其中任何一個都可以枚舉集合,與其他枚舉器互不影響。用來遍歷數據結構(表鏈、數組、集合類成員等)。

2、以下案例數組 作為內部數據結構,后期也可以換成數組,鏈表,樹,圖等等,而使用者卻不用關心這些內部數據表示,這就是迭代器的妙處所在。

存在的問題

1、對於需要遞歸遍歷的數據結構(如二叉樹),指示狀態可能就會變得相當復雜。為了減少實現此模式所帶來的挑戰,C# 2.0引入了迭代器, 新增了 yield 上下文關鍵字。
2、只能順序遍歷

枚舉器的原理

 

 

 

 

 

指針初始位置不在數據結構內(數組、表鏈、樹等結構)、第一次movenext()后, 數據結構項(表鏈 、數組等)不為空則返回true,否則為false

 

IEnumerator<string> enumerator = mc.BlackAndWhite().GetEnumerator();
        try
        {
                   //數據結構項(表鏈 、數組等)不為空則返回true,否則為false

            while (enumerator.MoveNext())
            {
                                //讀取當前指針位置處的值
                string shade = enumerator.Current;
                Console.Write(shade);
            }
        }
        finally
        {
            if (enumerator != null)
            {
                enumerator.Dispose();
            }
        }

 

 

IEnumerator接口

實現了IEnumerator接口的枚舉器包含3個public類型的成員Current、MoveNext()以及Reset()

在 IEnumerator 嵌套類中實現,以便可以創建多個枚舉器。

枚舉器內部可以用數組、表鏈、等其他數據結構。以下案例用數組

Current:返回當前處理的元素。

  • 它是只讀屬性。
  • 它返回object類型的引用,所以可以返回任何類型

MoveNext():把枚舉器位置向前到集合中的下一項方法

  • 它也返回布爾值,指示新的位置是有效位置還是已經超過了序列的尾部。
  • 如果新的位置是有效的
  • 如果新的位置是無效的(比如當前位置到達了尾部),方法返回false。
  • 枚舉器的原始位置在序列中的第一項之前,因此MoveNext必須在第一次使用Current之前調用。

  • int[] i = {1,1,1,2 };
    var  ie= i.GetEnumerator();
    //錯誤的寫法,原因是枚舉器位於集合中第一個元素之前,緊跟在創建枚舉器之后。 MoveNext 在讀取的值之前,必須調用以將枚舉數前移到集合的第一個元素 Current 。
    Console.Write(ie.Current);
    ie.MoveNext();

     

Reset():把位置重置為原始狀態方法(Reset 方法通常會拋出 NotImplementedException,因此不得進行調用。如果需要重新開始枚舉,只要新建一個枚舉器即可。)

枚舉器的實現

這種方式不好,不能創建多個枚舉實例。

using System;
using System.Collections;
namespace ConsoleEnum
{
    public class cars : IEnumerator,IEnumerable
    {
       private car[] carlist;
       int position = -1;
       //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)
           };
       }
       //IEnumerator and IEnumerable require these methods.
       public IEnumerator GetEnumerator()
       {
           return (IEnumerator)this;
       }
       //IEnumerator
       public bool MoveNext()
       {
           position++;
           return (position < carlist.Length);
       }
       //IEnumerable
       public void Reset()
       {
           position = -1;
       }
       //IEnumerable
       public object Current
       {
           get { return carlist[position];}
       }
    }
  }

本文中的示例盡量簡單(所以采用數組而不是其他數據解構(單項表鏈)),以更好地解釋這些接口的使用。不過該案例也反應出一個問題。

如果多線程訪問方法就會造成這個實例,由於MoveNext()是共享的。就會導致亂序。

若要使代碼更可靠並確保代碼使用當前最佳做法准則,請修改代碼,如下所示:

最佳做法

  • 將 IEnumerable和IEnumerator兩個接口的功能分開。集合類本身實現IEnumerable,集合類內部嵌套枚舉器 (繼承IEnumerator接口的類),以便可以創建多個枚舉器。
  • 枚舉器就像是序列中的“游標”或“書簽”。可以有多個“書簽”,移動其中任何一個都可以枚舉集合,與其他枚舉器互不影響。
  • 為 方法提供 Current 異常處理 IEnumerator 。 如果集合的內容更改,將 reset 調用 方法。 因此,當前枚舉器失效,您將收到 IndexOutOfRangeException 異常。 其他情況也可能導致此異常。 因此,實現 Try...Catch 塊以捕獲此異常並引發 InvalidOperationException 異常。
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);
      }
    }
}

 

 

枚舉器在集合中應用

首先、集合必須繼承IEnumerable 接口,該接口就是告訴別人他是可以枚舉的,他的內部已經實現了枚舉器。別人可以通過IEnumerable 接口 提供的GetEnumerator()方法獲得枚舉器。然后通過枚舉器的movenext訪問集合成員。

第二、然后需要在集合類的內部放一個枚舉器(嵌套一個現實 IEnumerator接口 的類)。然把集合自身的單向鏈表傳入枚舉器,枚舉器就像放在鏈表上的游標。

第三、別人就可以通過獲取調用集合類的GetEnumerator()方法,獲取到集合類的枚舉器。通過枚舉器順序的訪問集合。

 

實現IEnumerable接口的類有哪些:

數組、集合類 等等

 泛型枚舉接口

目前我們描述的枚舉接口都是非泛型版本。實際上,在大多數情況下你應該使用泛型版本IEnumerable<T>IEnumerator<T>。它們叫做泛型是因為使用了C#泛型(參見第17章),其使用方法和非泛型形式差不多。
兩者間的本質差別如下:

  • 對於非泛型接口形式:
    • IEnumerable接口的GetEnumerator方法返回實現IEnumerator枚舉器類的實例
    • 實現IEnumerator的類實現了Current屬性,它返回object的引用,然后我們必須把它轉化為實際類型的對象
  • 對於泛型接口形式:
    • IEnumerable<T>接口的GetEnumerator方法返回實現IEnumator<T>的枚舉器類的實例
    • 實現IEnumerator<T>的類實現了Current屬性,它返回實際類型的對象,而不是object基類的引用

需要重點注意的是,我們目前所看到的非泛型接口的實現不是類型安全的。它們返回object類型的引用,然后必須轉化為實際類型。
泛型接口的枚舉器是類型安全的,它返回實際類型的引用。如果要創建自己的可枚舉類,應該實現這些泛型接口。非泛型版本可用於C#2.0以前沒有泛型的遺留代碼。
盡管泛型版本和非泛型版本一樣簡單易用,但其結構略顯復雜。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM