1. 程式人生 > 其它 >C# - 列舉器和迭代器

C# - 列舉器和迭代器

# 前述

在C#裡 , foreach語句可以用來遍歷陣列中的元素 ,如下所示 :

    int[] arr1 = { 10 , 11 , 12 , 13} ;     //定義陣列
    foreach ( int item in arr )             //列舉元素
        Console.WtitLine( $"Item value : {item}" );

這段程式碼將會產生如下的輸出 :

    Item value: 10 
    Item value: 11
    Item value: 12
    Item value: 13

你應該感到好奇 —— 為什麼foreach語句可以自動遍歷陣列中的每一個元素 ? 要回答這個問題 ,我們得先明確 : foreach語句只是一種語法糖 ,或者更通俗的說 —— 它只是一種方便了程式設計師的簡化寫法而已 。

所以 ,上述程式碼的等價非簡化寫法是 :

    static void Main()
    {
        int[] arr1 = { 10 , 11 , 12 , 13 } ;   //建立陣列
        
        //獲取陣列物件的列舉器 
        IEnumerator ie = arr1.GetEnumerator();
        
        //移到下一項
        while( ie.MoveNext() )  
        {
            int item = (int)ie.Current ;    //ie.current預設是Object型別,轉型為int ;
            Console.WriteLine($"Item value: {item}");  //輸出
        }
    }

這段程式碼的輸出 ,和上述使用foreach語法糖的程式碼相一致 :

    Item value: 10 
    Item value: 11
    Item value: 12
    Item value: 13

這段程式碼才是foreach語法糖的實質 ,也就是說 ,在經過C#編譯器的編譯之後 ,foreach語句會被編譯成這種形式的程式碼 ,而這也是程式執行期間具體被執行的程式碼 ;

由上述程式碼可以看出 ,使用foreach語句遍歷陣列的本質是 : 通過呼叫陣列物件的GetEnumerator()方法可以獲取一個叫做列舉器的物件(實現了IEnumerator介面的類的例項) ,通過操縱這個物件 , 可以依次序的控制列舉器所迭代的陣列元素 。而陣列型別的getEnumerator()方法是通過繼承並且實現IEnumerable介面而來的,我們把實現了IEnumerable介面的類叫做可列舉型別

,例如陣列類就是可列舉型別 ;

我們從上述對foreach的探討 ,引出了兩個全新的概念 —— 列舉器和可列舉型別 ,這也是本文所要探討的焦點 ,接下來 , 我們將通過一系列的示例 ,來探討它們的實現原理 和運作機制 ;


#列舉器的本質 - 實現IEnumerator介面的類的例項

在上文中 ,我們提到列舉器是實現了IEnumerator介面的類的例項 ,所以我們來看一下IEnumerator介面長啥樣 :

namespace System.Collections
{
    //
    // 摘要:
    //     Supports a simple iteration over a non-generic collection.
    public interface IEnumerator
    {
        //
        // 摘要:
        //     Gets the element in the collection at the current position of the enumerator.
        //
        // 返回結果:
        //     The element in the collection at the current position of the enumerator.
        object? Current { get; }

        //
        // 摘要:
        //     Advances the enumerator to the next element of the collection.
        //
        // 返回結果:
        //     true if the enumerator was successfully advanced to the next element; false if
        //     the enumerator has passed the end of the collection.
        //
        // 異常:
        //   T:System.InvalidOperationException:
        //     The collection was modified after the enumerator was created.
        bool MoveNext();
        //
        // 摘要:
        //     Sets the enumerator to its initial position, which is before the first element
        //     in the collection.
        //
        // 異常:
        //   T:System.InvalidOperationException:
        //     The collection was modified after the enumerator was created.
        void Reset();
    }
}

正如上述程式碼顯示的 ,IEnumerator介面包含了3個需要被繼承它的類所實現的函式成員 ,分別是 :

  • Current : 它是一個只讀的屬性 , 它返回的是object型別的引用 ,所以可以返回任何型別的物件 ,或者通俗點說 —— Current返回序列中的當前位置項
  • MoveNext : 把列舉器位置前進到集合中下一項的方法 。它返回布林值 ,指示新的位置是有效位置還是已經超過了序列的尾部 ,即 ,如果新的位置是有效的 ,方法返回true ,如果是無效的(比如當前位置到達了尾部),方法返回false 。因為列舉器的原始位置在序列的第一項之前 ,因此MoveNext必須在第一次使用Current之前呼叫 ;
  • Reset : 把位置重置為原始狀態的方法 ;

下面展示一個列舉器類ArrEnumerator ,可以把它看成列舉器類的標準模板, 其繼承並且實現了介面IEnumerator :

    using System ;
    using System.Collections ;
    class ArrEnumerator : IEnumerator
    {
        string[] Arrs ;       // 這個Arrs是關鍵,通過它儲存將要被列舉器迭代的序列的副本(其實就是陣列) 。 作為演示 ,這裡把它置為string[]型別 ,它也可以是int[]、float[]或者其它。
        int position = -1 ;  // 序列(陣列元素)位置 ,預設是-1 ;
        
        //建構函式
        public ArrEnumerator( string[] theArrs )  // theArrs是需要被迭代的序列(陣列)物件的引用
        {
            // 通過Arrs儲存副本 ;注意 : Arrs和theArrs是對不同序列物件的引用 
            Arrs = new string[theArrs.Length] ;
            for( int i = 0 ; i < theArrs.Length ;  i++ )
            {
                Arrs[i] = theArrs[i] ;
            }
        }
        
        //實現Current , 返回列舉器所指向的當前序列元素 ,返回的是一個object型別的引用
        public object Current
        {
            get
            {
                if( position == -1 )
                    throw new InvalidOperationException() ;
                if( position >= Arrs.Length )
                    throw new InvalidOperationException() ;
                    
                return Arrs[position] ;
            }     
        }
        
        //實現MoveNext() ,以便移動到序列的下一個位置
        public bool MoveNext() 
        {
            if( position < Arrs.Length - 1 )
            {
                position++ ;
                return true ;
            }
            else 
            {
                return false ;
            }
        }
        
        // 實現reset ,將列舉器恢復到初始狀態
        public void reset()
        {
            position = -1 ;
        }
        
    }

上述程式碼描述了列舉器類的標準模板,雖然對IEnumerator介面成員的具體實現方式可能不同 。

我們可以從上述程式碼看出 ,列舉器是有狀態的 , 這個狀態是用position來描述的 ,position的值不同 , 我們便說它的狀態是不同的 。而狀態的切換 , 即position值的+1遞增 , 是由MoveNext()方法來實現的 。每次MoveNext()之後 , 只要position< arrs.Lenght ,那麼新的位置(狀態)就是有效的 ,moveNext便會返回true 。否則 , 如果移動到的新的位置(狀態)是無效的 ,即position >= arrs.Length , 那麼就會返回false ;當狀態為 position == arrs.Length時 ,繼續呼叫moveNext()將總會返回false ,此時,如果在呼叫Current將丟擲異常 。


# 可列舉型別的本質 - 實現了IEnumerable介面

可列舉類是指實現了IEnumerable介面的類 。IEnumerable介面只有一個成員 —— GetEnumerator方法 , 它負責返回物件的列舉器 ;

下面的這段程式碼是IEnumerable介面的具體實現 :

namespace System.Collections
{
    //
    // 摘要:
    //     Exposes an enumerator, which supports a simple iteration over a non-generic collection.
    public interface IEnumerable
    {
        //
        // 摘要:
        //     Returns an enumerator that iterates through a collection.
        //
        // 返回結果:
        //     An System.Collections.IEnumerator object that can be used to iterate through
        //     the collection.
        IEnumerator GetEnumerator();
    }
}

正如前面所說的 , IEnumerable介面只有一個成員 —— 方法 GetEnumerator ;

接下來 , 我們將通過下面的這段程式碼 ,演示可列舉型別的標準宣告形式 ,或者說是標準模板:

    using System.Collections ;
    class MyColors : IEnumerable
    {
        string[] colors = { "Red" , "Yellow" , "Blue" } ;
        
        public IEnumerator GetEnumerator() 
        {
            return new ColorEnumerator(Colors) ;
        }
        
    }
    

本文轉自 https://juejin.cn/post/7019678517482749988,如有侵權,請聯絡刪除。