2009-10-28 48 views
5

我一直在想IEnumerator.Reset()方法。我在MSDN文檔中看到,它只在那裏用於COM互操作。作爲一名C++程序員,它在我看來就像是一個IEnumerator,它支持Reset就是我所說的forward iterator,而不支持ResetIEnumerator確實是input iteratorC#能否像C++迭代器那樣區分各種枚舉類型?

所以我的問題的第一部分是,這種理解是否正確?

我的問題的第二部分是,如果在輸入迭代器和前向迭代器(或者如果你喜歡的話,是「枚舉器」)之間做出區分,它會對C#有什麼好處嗎?它會不會幫助消除程序員之間的一些混淆,例如在SO question about cloning iterators中找到的那個?

編輯:澄清正向和輸入迭代器。輸入迭代器只保證您可以枚舉一次集合的成員(或來自生成器函數或輸入流)的成員。這正是IEnumerator在C#中的工作原理。是否可以第二次枚舉,取決於是否支持Reset。前向迭代器不具有此限制。您可以隨時列舉所有成員。

一些C#程序員不會爲什麼不能在多通道算法中可靠地使用IEnumerator。考慮以下情況:

void PrintContents(IEnumerator<int> xs) 
{ 
    while (iter.MoveNext()) 
    Console.WriteLine(iter.Current); 
    iter.Reset(); 
    while (iter.MoveNext()) 
    Console.WriteLine(iter.Current); 
} 

如果我們在這方面呼籲PrintContents,沒問題:

List<int> ys = new List<int>() { 1, 2, 3 } 
PrintContents(ys.GetEnumerator()); 

但是看看下面:

IEnumerable<int> GenerateInts() { 
    System.Random rnd = new System.Random(); 
    for (int i=0; i < 10; ++i) 
    yield return Rnd.Next(); 
} 

PrintContents(GenerateInts()); 

如果IEnumerator支持Reset,在換言之,它支持多遍算法,然後每次迭代集合時都會有所不同。這將是不可取的,因爲這將是令人驚訝的行爲。這個例子有點僞造,但它確實發生在現實世界中(例如從文件流中讀取)。

+1

我想你是指'IEnumerator.Reset',而不是'IEnumerable .Reset',對吧? – 2009-10-28 02:50:30

+0

是的,謝謝!對於那個很抱歉。 – cdiggins 2009-10-28 03:04:08

+0

有趣的問題。但也許你應該解釋一下C++ - 講一點,因爲很多C#程序員都會看到這一點。確切地說(特別是它們的單通道/多通道功能,這與這個問題真正相關)可能並不明顯,輸入迭代器和前向迭代器是什麼, – jalf 2009-10-28 09:57:45

回答

2

有趣的問題。我的意見是,當然C#會好處。但是,添加並不容易。

C++中存在的區別在於它具有更靈活的類型系統。在C#中,您沒有強大的通用方法來克隆對象,這是表示前向迭代器(以支持多遍迭代)所必需的。當然,爲了實現這一點,您還需要支持雙向和隨機訪問迭代器/枚舉器。爲了讓這些工作順利進行,您確實需要某種形式的鴨子輸入,就像C++模板一樣。

最終,這兩個概念的範圍是不同的。

在C++中,迭代器應該表示您需要了解的一系列值的所有信息。給定一對迭代器,我不需要原始容器。我可以排序,我可以搜索,我可以像我喜歡的那樣操縱和複製元素。原始容器不在圖片中。

在C#中,枚舉器並不意味着做得相當多。最終,它們只是設計用來讓您以線性方式運行序列。

至於Reset(),人們普遍認爲把它加在首位是一個錯誤。如果它已經工作並正確實施,那麼是的,你可以說你的枚舉器類似於轉發迭代器,但總的來說,最好將它忽略爲一個錯誤。然後所有的枚舉器只與輸入迭代器相似。

不幸的是。

+0

喝完一杯咖啡之後,我看不出爲什麼現在需要克隆多通迭代器(忽略前向迭代器的全部要求)。 – cdiggins 2009-10-28 11:58:36

+0

你還會怎樣在前向迭代器上做多次傳遞?它不是雙向的,所以你不能回到你所在的位置,然後再次迭代。你必須創建一個迭代器的副本,所以你有兩個迭代器指向序列中的相同位置,然後它們可以單獨遞增。 – jalf 2009-10-28 12:41:52

+0

如果您認爲'Reset()'方法可以替代克隆,那麼可以。除了復位以外,只會將您帶回序列中的一個固定點(開始處)。但是一個前向迭代器應該能夠對數據的一個子集進行多次傳遞(比如,最後三個元素)。重置在這裏並沒有真正的幫助。 (或者至少,如果你必須進行完全重置,並且遍歷整個序列只是爲了回到你想要執行多遍遍歷的地方,那麼它會變得非常慢) – jalf 2009-10-28 12:43:34

3

Reset是一個很大的錯誤。我打電話給Reset上的shenanigans。在我看來,反映您在.NET類型系統中「正向迭代器」和「輸入迭代器」之間所作區別的正確方法是區分IEnumerable<T>IEnumerator<T>

另請參見this answer,其中微軟的Eric Lippert(無疑,我的觀點只不過是他是一個擁有更多證書的人,而不是我必須聲稱這是一個設計錯誤)提出了類似的觀點註釋。另請參閱his awesome blog

+1

爲鏈接。但是,我不同意你的類比。 IEnumerable對C++容器更爲貼切http://www.sgi.com/tech/stl/Container.html – cdiggins 2009-10-28 03:05:55

-1

我不這麼認爲。我將調用IEnumerable一個前向迭代器和一個輸入迭代器。它不允許你倒退,或修改底層集合。隨着foreach關鍵字的增加,迭代器在大多數情況下幾乎都是沒有想到的。

意見: 輸入迭代器之間(讓每一個)與輸出迭代器(做一些事情來每一個)不同的是過於瑣碎的理由的除了框架。另外,爲了執行輸出迭代器,您需要將委託傳遞給迭代器。對於C#程序員來說,輸入迭代器似乎更自然。如果程序員想要隨機訪問,還有IList<T>

+0

在C++中,前向迭代器支持多遍算法,但這並不是您可以靠IEnumerable/IEnumerator的。例如:如果IEnumerable是從yield函數生成的。 – cdiggins 2009-10-28 03:27:57

+0

我不明白爲什麼IEnumerable不支持多通道算法。自定義迭代器(使用yield return)創建IEnumerator的一個實例。您可以讓多個IEnumerator對象一次迭代同一個集合。 – 2009-10-28 03:34:14

+2

但是隻給出一個IEnumerator,你不能對集合進行多次傳遞。您需要訪問基礎集合才能執行多次迭代,這是一個缺點。 (因爲在C++中,迭代器被設計爲覆蓋你所有的迭代需求,在C#中,一些相當基本的算法(任何需要多次傳遞的東西)必須打破抽象並且需要訪問底層容器) – jalf 2009-10-28 09:41:31

1

從C#的角度出發:

你幾乎從來不直接使用IEnumerator。通常你會執行foreach聲明,該聲明需要IEnumerable

IEnumerable _myCollection; 
... 
foreach (var item in _myCollection) { /* Do something */ } 

你也不會傳遞IEnumerator。如果您想傳遞需要迭代的集合,則通過IEnumerable。由於IEnumerable具有單個函數,該函數返回IEnumerator,因此它可用於多次迭代集合(多遍)。

IEnumerator不需要Reset()函數,因爲如果你想重新開始,你只需扔掉舊的(垃圾收集)並獲得一個新的。

1

如果有一種方法詢問IEnumerator<T>有哪些可以支持的功能以及它會做出什麼承諾,.NET框架將會受益匪淺。這些功能在IEnumerable<T>中也會有所幫助,但是能夠提出枚舉器的問題將允許代碼可以從像ReadOnlyCollection這樣的包裝器接收枚舉器,以改進方式使用底層集合,而不必涉及包裝器。

鑑於任何能夠枚舉整體且不會太大的集合的枚舉器,我們可以從中產生一個總是產生相同序列的項目(特別是剩下的項目集合)的IEnumerable<T>枚舉器)通過將其整個內容讀取到一個數組中,處理和放棄枚舉器,並從數組中獲取一個枚舉器(使用該枚舉器代替原始的廢棄枚舉器),將數組包裝在ReadOnlyCollection<T>中,然後返回該數組。儘管這樣的方法可以適用於滿足上述標準的任何種類的可收集的收集品,但其中大多數收集品的效率非常低。有一種方法要求枚舉員在不可變的IEnumerable<T>中產生其剩餘內容將允許多種枚舉器更有效地執行指定的操作。