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,如有侵權,請聯絡刪除。