[英]Why do we need iterators in c#?
有人可以提供關於迭代器使用的真實例子。 我試着搜索谷歌,但對答案不滿意。
您可能聽說過數組和容器 - 存儲其他對象列表的對象。
但是為了使對象表示列表,它實際上不必“存儲”列表。 它所要做的就是為您提供允許您獲取列表項的方法或屬性。
在.NET框架中,接口IEnumerable是一個對象必須支持在這個意義上被視為“列表”。
為了簡化它(省略一些歷史包袱):
public interface IEnumerable<T>
{
IEnumerator<T> GetEnumerator();
}
所以你可以從中獲得一個枚舉器。 該界面(再次,略微簡化以消除分散注意力的噪音):
public interface IEnumerator<T>
{
bool MoveNext();
T Current { get; }
}
因此,要遍歷列表,您可以這樣做:
var e = list.GetEnumerator();
while (e.MoveNext())
{
var item = e.Current;
// blah
}
foreach
關鍵字可以整齊地捕獲此模式:
foreach (var item in list)
// blah
但是如何創建一種新的列表呢? 是的,我們可以使用List<T>
並填充項目。 但是,如果我們想要在需要時“動態”發現這些物品呢? 這有一個優點,即客戶端可以在前三項之后放棄迭代,並且它們不必“支付生成整個列表的成本”。
手動實現這種懶惰列表會很麻煩。 我們將必須寫出兩個類,一個通過實施以表示該列表IEnumerable<T>
而另一個通過實施以表示活性枚舉操作IEnumerator<T>
迭代器方法為我們做了所有艱苦的工作。 我們寫道:
IEnumerable<int> GetNumbers(int stop)
{
for (int n = 0; n < stop; n++)
yield return n;
}
編譯器將此轉換為兩個類。 調用該方法等同於構造表示該列表的類的對象。
迭代器是一種抽象,它將集合中的位置概念與集合本身分離。 迭代器是一個單獨的對象,存儲必要的狀態以定位集合中的項目並移動到集合中的下一個項目。 我已經看到集合在集合中保持該狀態(即當前位置),但通常最好將該狀態移動到外部對象。 除此之外,它還允許您使用多個迭代器迭代相同的集合。
簡單示例:生成整數序列的函數:
static IEnumerable<int> GetSequence(int fromValue, int toValue)
{
if (toValue >= fromValue)
{
for (int i = fromValue; i <= toValue; i++)
{
yield return i;
}
}
else
{
for (int i = fromValue; i >= toValue; i--)
{
yield return i;
}
}
}
要在沒有迭代器的情況下執行此操作,您需要創建一個數組然后枚舉它...
在課堂上通過學生進行迭代
Iterator設計模式為我們提供了枚舉項目或數組列表的常用方法,同時隱藏了列表實現的細節。 這樣可以更清晰地使用數組對象,並從客戶端隱藏不必要的信息,最終實現更好的代碼重用,增強的可維護性和更少的錯誤。 迭代器模式可以枚舉項目列表,而不管它們的實際存儲類型如何。
他們非常適合的一些事情:
a)在保持代碼整潔的同時“感知性能” - 與其他處理邏輯分離的東西的迭代。
b)當您要迭代的項目數量未知時。
雖然兩者都可以通過其他方式完成,但是使用迭代器可以使代碼變得更好更整潔,因為調用迭代器的人不需要擔心它如何找到迭代的東西......
現實生活中的例子:枚舉目錄和文件,找到滿足某些條件的第一個[n],例如包含某個字符串或序列等的文件......
除了其他一切,迭代懶惰型序列 - IEnumerators。 可以在迭代步驟中評估/初始化這種序列的每個下一個元素,這使得可以使用有限量的資源迭代無限序列......
規范和最簡單的例子是它使無限序列成為可能,而不必自己編寫類來完成這一過程的復雜性:
// generate every prime number
public IEnumerator<int> GetPrimeEnumerator()
{
yield return 2;
var primes = new List<int>();
primesSoFar.Add(2);
Func<int, bool> IsPrime = n => primes.TakeWhile(
p => p <= (int)Math.Sqrt(n)).FirstOrDefault(p => n % p == 0) == 0;
for (int i = 3; true; i += 2)
{
if (IsPrime(i))
{
yield return i;
primes.Add(i);
}
}
}
顯然,除非你使用BigInt而不是int,否則這不會真正無限,但它會給你一個想法。 為每個生成的序列編寫此代碼(或類似代碼)將是乏味且容易出錯的。 迭代器為你做這件事。 如果以上示例看起來太復雜,請考慮:
// generate every power of a number from start^0 to start^n
public IEnumerator<int> GetPowersEnumerator(int start)
{
yield return 1; // anything ^0 is 1
var x = start;
while(true)
{
yield return x;
x *= start;
}
}
他們雖然付出了代價。 它們的惰性行為意味着您無法發現常見錯誤(空參數等),直到生成器首次使用而不是創建而不先寫入包裝函數進行檢查。 如果遞歸使用,當前的實現也非常糟糕(1)。
對於像樹和對象圖這樣的復雜結構的詳細枚舉更容易編寫,因為狀態維護主要是為您完成的,您必須簡單地編寫代碼來訪問每個項目而不必擔心回到它。
迭代器是實現IEnumerator接口的簡單方法。 您只需創建一個逐個返回值的方法,而不是創建具有接口所需的方法和屬性的類,編譯器將創建一個具有實現接口所需的方法和屬性的類。
例如,如果您有一個大的數字列表,並且想要返回一個集合,其中每個數字乘以2,您可以創建一個返回數字的迭代器,而不是在內存中創建列表的副本:
public IEnumerable<int> GetDouble() {
foreach (int n in originalList) yield return n * 2;
}
在C#3中,您可以使用擴展方法和lambda表達式執行類似的操作:
originalList.Select(n => n * 2)
或者使用LINQ:
from n in originalList select n * 2
IEnumerator<Question> myIterator = listOfStackOverFlowQuestions.GetEnumerator();
while (myIterator.MoveNext())
{
Question q;
q = myIterator.Current;
if (q.Pertinent == true)
PublishQuestion(q);
else
SendMessage(q.Author.EmailAddress, "Your question has been rejected");
}
foreach (Question q in listOfStackOverFlowQuestions)
{
if (q.Pertinent == true)
PublishQuestion(q);
else
SendMessage(q.Author.EmailAddress, "Your question has been rejected");
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.