详解C#迭代器
一、迭代器(Iterator)通過持有迭代狀態(tài)可以獲取當前迭代元素并且識別下一個需要迭代的元素,從而可以遍歷集合中每一個元素而不用了解集合的具體實現(xiàn)方式;
實現(xiàn)迭代器功能的方法被稱為迭代器方法,迭代器方法的返回值類型可以是以下4種接口類型中任意一種:位于命名空間System.Collections中的IEnumerable、IEnumerator和位于命名空間System.Collections.Generic中的IEnumerable<T>、IEnumerator<T>,其中接口IEnumerable和IEnumerator代碼:
public interface IEnumerable
{
IEnumerator GetEnumerator();
}
public interface IEnumerator
{
object Current { get; }
bool MoveNext();
void Reset();
}
※對應泛型接口IEnumerable<T>和IEnumerator<T>代碼:
public interface IEnumerable<out T> : IEnumerable
{
IEnumerator<T> GetEnumerator();
}
public interface IEnumerator<out T> : IDisposable, IEnumerator
{
T Current { get; }
}
※接口IEnumerable中聲明了獲取IEnumerator類型對象的方法GetEnumerator(),接口IEnumerator中通過屬性Current和方法MoveNext()實現(xiàn)迭代器功能;自定義類型可以通過繼承接口IEnumerable擁有迭代器功能,而接口IEnumerator為迭代器功能提供具體實現(xiàn),設計二者符合單一職責原則,也因此可以對同一個對象同時進行多次迭代;
※get訪問器也可以聲明為迭代器方法,但事件、構造函數(shù)和析構函數(shù)不可以聲明為迭代器方法;迭代器方法不能包含引用參數(shù);
二、迭代器方法的內部使用狀態(tài)機實現(xiàn),但可以使用yield關鍵字快速實現(xiàn)迭代器方法,使用yield return語句添加需要迭代的元素,在首次迭代時,會一直執(zhí)行到第一個yield return語句并保存當前迭代狀態(tài),在接下來的每次迭代過程中都會從暫停的位置繼續(xù)執(zhí)行到下一個yield return語句并保存迭代狀態(tài)(MoveNext()方法返回true并將當前迭代的值賦值給Current),直到到達迭代器的結尾(MoveNext()方法返回false)完成本次迭代;也可以在迭代過程中使用yield break語句立刻結束本次迭代:
IEnumerable<int> MyIterator()
{
yield return 10;
yield return 20;
}
※其中yield return語句的返回值需要可以隱式轉換為迭代器方法返回值IEnumerable<T>中類型參數(shù)的類型;
※這是編譯器為我們準備的一種語法糖,編譯器會將其轉換為使用狀態(tài)機實現(xiàn)的實現(xiàn)了接口IEnumerable<T>的嵌套類,查看迭代器方法的IL代碼,可以看到編譯器生成的狀態(tài)機嵌套類:
三、對于實現(xiàn)了接口IEnumerable或IEnumerable<T>的類型的對象,可以使用foreach對其進行遍歷:
foreach (int item in MyIterator())
{
//do…其中item為int類型,值分別為10和20
}
※其中item實際上是類型中GetEnumerator()方法返回值類型中的Current屬性,因此其類型即該屬性的類型,通常可以使用var表示,由編譯器進行推斷,例如對哈希表進行遍歷時:
foreach (var item in myHashTable)
{
//do…其中item為DictionaryEntry類型,item.Key and item.Value
}
※由于屬性Current只有get訪問器,因此不能在foreach循環(huán)中對item進行賦值(=運算符操作)操作,但可以對其對象中的成員進行修改和調用,詳見;
※在遍歷內置集合的對象時,如果集合長度發(fā)生改變,則極有可能紊亂迭代過程,因此在迭代這些集合對象時不能進行任何修改集合長度的操作,否則會拋出異常InvalidOperationException;
四、除使用foreach外,還可以手動進行迭代,對于實現(xiàn)了泛型接口的類型的對象在手動迭代時還需要使用using語句以在其迭代完成后調用Dispose()方法:
using (IEnumerator<int> enumerator = MyIterator().GetEnumerator())
{
//此時myEnmerator.Current為其類型的默認值
while (enumerator.MoveNext())
{
//do…其中可以通過enumerator.Current訪問迭代器的值
}
}
1.對于只實現(xiàn)了接口IEnumerator或IEnumerator<T>的類型的對象,只能手動進行迭代:
IEnumerator<int> MyIterator()
{
yield return 1;
yield return 2;
}
using (IEnumerator<int> enumerator = MyIterator())
{
while (enumerator.MoveNext())
{
//do…
}
}
2.可以通過實現(xiàn)接口IEnumerable或IEnumerable<T>來自定義迭代器類型,此時不必手動實現(xiàn)IEnumerator或IEnumerator<T>接口,編譯器會自動實現(xiàn)該接口中的Current屬性、MoveNext()、Reset()和Dispose()方法:
public class MyIterator : IEnumerable<int>
{
public IEnumerator<int> GetEnumerator()
{
yield return 10;
yield return 20;
}
IEnumerator IEnumerable.GetEnumerator()
{
Console.WriteLine("Non-Generic GetEnumerator");
return GetEnumerator(); //注意這里不是yield return
//或者手動進行迭代:
//using (IEnumerator<int> iterator = this.GetEnumerator())
//{
//while (iterator.MoveNext())
//{
//yield return iterator.Current;
//}
//}
}
}
※此時,在使用foreach遍歷時,如果使用var,會由編譯器自動推斷item為int類型,并調用泛型接口的實現(xiàn),如果將item的類型指定為object類型,則依然會調用泛型接口的實現(xiàn),但會產生裝箱操作:
foreach (var item in new MyIterator()) //或顯式指定為int:int item
{
Console.WriteLine(item); //10 20
}
※此時,在使用foreach遍歷時,如果將其轉換為IEnumerable類型的對象,則會調用非泛型接口的實現(xiàn),編譯器將自動推斷item為object類型,并產生裝箱操作:
foreach (var item in (IEnumerable)new MyIterator()) //或顯式指定為object:object item
{
Console.WriteLine(item); //Non-Generic GetEnumerator 10 20
}
3.直接調用迭代器方法或迭代器類型中自動生成的Reset()方法時會拋出異常NotSupportedException,若要從頭開始重新迭代,必須獲取新的迭代器,或在迭代器類型中手動實現(xiàn)Reset()方法;
如果您覺得閱讀本文對您有幫助,請點一下“推薦”按鈕,您的認可是我寫作的最大動力!
作者:Minotauros
出處:https://www.cnblogs.com/minotauros/
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。
總結
- 上一篇: Linux的ldconfig命令和ldd
- 下一篇: cpu内部组成