簡體   English   中英

為什么我們需要在c#中使用迭代器?

[英]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)。

對於像樹和對象圖這樣的復雜結構的詳細枚舉更容易編寫,因為狀態維護主要是為您完成的,您必須簡單地編寫代碼來訪問每個項目而不必擔心回到它。


  1. 我不輕易使用這個詞 - O(n)迭代可以變成O(N ^ 2)

迭代器是實現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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM