2015-07-10 52 views
12

我讀過埃裏克的有關的foreach枚舉和對不同場景的文章here其中的foreach可以工作C#`foreach`行爲 - 澄清?

爲了防止舊的C#版本做拳擊,C#團隊啓用鴨子類型爲的foreach上運行。非IEnumerable集合(A公共GetEnumerator返回的東西,有公共MoveNextCurrent屬性是足夠了(

所以,埃裏克寫了sample

class MyIntegers : IEnumerable 
{ 
    public class MyEnumerator : IEnumerator 
    { 
    private int index = 0; 
    object IEnumerator.Current { return this.Current; } 
    int Current { return index * index; } 
    public bool MoveNext() 
    { 
     if (index > 10) return false; 
     ++index; 
     return true; 
    } 
    } 
    public MyEnumerator GetEnumerator() { return new MyEnumerator(); } 
    IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); } 
} 

但我相信它有一些輸入錯誤(缺少訪問者在Current屬性執行)它阻止它編譯(我已經給他發了電子郵件)。

反正這裏是一個工作版本:

class MyIntegers : IEnumerable 
{ 
    public class MyEnumerator : IEnumerator 
    { 
    private int index = 0; 
     public void Reset() 
     { 
      throw new NotImplementedException(); 
     } 

     object IEnumerator.Current { 
      get { return this.Current; } 
     } 
    int Current { 
     get { return index*index; } 
    } 
    public bool MoveNext() 
    { 
     if (index > 10) return false; 
     ++index; 
     return true; 
    } 
    } 
    public MyEnumerator GetEnumerator() { return new MyEnumerator(); } 
    IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); } 
} 

確定。

根據MSDN

A型C被說成是一個collection type如果它實現了 System.Collections.IEnumerable接口通過滿足的以下條件的所有實現 collection pattern

  • C包含一個具有簽名GetEnumerator()的公共實例方法,該方法返回結構類型,類類型或接口類型,在以下文本中稱爲E.

  • E包含具有簽名MoveNext()和返回類型bool的公共實例方法。

  • E包含一個名爲Current的公共實例屬性,允許讀取當前值。該屬性的類型被認爲是集合類型的元素類型。

好的。讓我們的文檔匹配埃裏克的樣本

Eric的樣品說是collection type,因爲它實現了System.Collections.IEnumerable接口(雖然明確)。但它是不是(!) a collection pattern由於子彈3:MyEnumerator確實不是名爲當前的公共實例屬性。

MSDN說:

如果集合表達是實現 集合圖案(如上所定義)的類型,在foreach 語句的膨脹是:

E enumerator = (collection).GetEnumerator(); 
try { 
    while (enumerator.MoveNext()) { 
     ElementType element = (ElementType)enumerator.Current; 
     statement; 
    } 
} 
finally { 
    IDisposable disposable = enumerator as System.IDisposable; 
    if (disposable != null) disposable.Dispose(); 
} 

否則,集合表達式是一種實現(!)■ System.IEnumerable,和foreach語句的擴展:

IEnumerator enumerator = 
     ((System.Collections.IEnumerable)(collection)).GetEnumerator(); 
try { 
    while (enumerator.MoveNext()) { 
     ElementType element = (ElementType)enumerator.Current; 
     statement; 
    } 
} 
finally { 
    IDisposable disposable = enumerator as System.IDisposable; 
    if (disposable != null) disposable.Dispose(); 
} 

問題#1

看來,Eric的樣品既沒有實現 collection pattern也不System.IEnumerable - 所以它不應該匹配上述條件的任何

foreach (var element in (new MyIntegers() as IEnumerable)) 
      { 
       Console.WriteLine(element); 
      } 

問題2

爲什麼我不得不提new MyIntegers() as IEnumerable:那爲什麼我仍然可以通過迭代呢?它已經IEnumerable的(!),甚至在那之後,是不是編譯器已經通過自身通過鑄造做的工作:

((System.Collections.IEnumerable)(collection)).GetEnumerator() ? 

正是在這裏:

IEnumerator enumerator = 
      ((System.Collections.IEnumerable)(collection)).GetEnumerator(); 
    try { 
     while (enumerator.MoveNext()) { 
     ... 

那麼,爲什麼它仍然想讓我像易數一樣提及?

+1

現在我們知道這個問題主要是關於'System.IEnumerable'輸入錯誤,我還會提到,這個語言規範應該允許您的示例在沒有投射的情況下通過回退到使用IEnumerable接口。當收集模式部分實現時,規範從未提及引發錯誤(這是我在回答中強調C#5.0規範解釋了此錯誤的原因)。 – Lukazoid

回答

6

MyEnumerator不具有所需的公共方法

是它 - 或者更確切地說,它會如果Current是公開的。所有這一切需要的是,它具有:

  • 公共性,可讀性Current財產
  • 公共MoveNext()方法無類型參數返回bool

缺乏public這裏只是另一個錯字,基本上。事實上,這個例子並沒有達到它的目的(防止裝箱)。它使用IEnumerable實現,因爲您使用的是new MyIntegers() as IEnumerable - 因此表達式類型爲IEnumerable,並且它僅使用整個接口。

你聲稱它不實現IEnumerable(它是System.Collections.IEnumerable,順便說一句),但它確實使用顯式接口實現。

最簡單的方法來測試這種事情沒有實施IEnumerable可言:

using System; 

class BizarreCollection 
{ 
    public Enumerator GetEnumerator() 
    { 
     return new Enumerator(); 
    } 

    public class Enumerator 
    { 
     private int index = 0; 

     public bool MoveNext() 
     { 
      if (index == 10) 
      { 
       return false; 
      } 
      index++; 
      return true; 
     } 

     public int Current { get { return index; } } 
    } 
} 

class Test 
{ 
    static void Main(string[] args) 
    { 
     foreach (var item in new BizarreCollection()) 
     { 
      Console.WriteLine(item); 
     } 
    } 
} 

現在,如果你讓Current私有的,它不會編譯。

+4

當前不公開(我不是downvoter)。 –

+1

@RoyiNamir:啊,真的 - 那只是另一個錯字。正如你所說,現有的例子不會編譯 - 放入'public',這很好。將編輯。 –

+2

但它已經像所有沒有公開的工作http://i.imgur.com/gxqaEeq.png –

4

對MSDN上System.IEnumerable的引用只不過是舊語言規範中的一個錯字,不存在這樣的接口,我相信它應該指的是System.Collections.IEnumerable

您應該確實閱讀您正在使用的C#版本的語言規範,C#5.0語言規範可用here

爲什麼沒有投中一個錯誤這個例子的結果,而不是回落到使用System.Collections.IEnumerable一些進一步的信息:

在規範foreach(第8.8.4),你會看到的規則略有變化(一些步驟已經被切割爲簡潔起見):

  • 上具有標識符的GetEnumerator和無類型參數的類型X執行成員查找。如果成員查找不產生匹配,或者產生歧義,或者產生不是方法組的匹配,請檢查下面描述的可枚舉接口。如果成員查找產生除方法組或不匹配以外的任何內容,建議發出警告。
  • 使用生成的方法組和空參數列表執行重載解析。如果重載解析導致沒有適用的方法,導致模糊不清,或者導致單個最佳方法,但是該方法是靜態或未公開,請檢查可枚舉接口,如下所述。如果重載解析產生除明確的公共實例方法或沒有適用方法之外的任何內容,則建議發出警告。
  • 如果GetEnumerator方法的返回類型E不是類,結構或接口類型,則會產生錯誤,並且不會採取進一步的步驟。
  • 成員查找在E上執行,標識符爲Current並且沒有類型參數。如果成員查找不匹配,則結果爲錯誤,或者結果爲除允許讀取的公共實例屬性之外的任何結果,則會產生錯誤,並且不會採取進一步措施。
  • 集合類型爲X,枚舉類型爲E,元素類型爲當前屬性的類型。

所以從第一點,我們會發現public MyEnumerator GetEnumerator(),第二和第三子彈穿過而不會出現錯誤。當我們到達第四個要點時,沒有公共成員Current可用,導致您在沒有轉換的情況下看到的錯誤,這種確切的情況從來沒有給編譯器提供查找可枚舉接口的機會。

當您明確地將您的實例轉換爲IEnumerable時,由於類型IEnumerable和關聯的IEnumerator滿足所有要求,所有的要求都得到滿足。

從文檔

另外:

上述步驟,如果成功的話,毫無疑義地產生集合類型C,枚舉類型E和元素類型T.形式

foreach (V v in x) embedded-statement 

的foreach語句隨後擴展爲:

{ 
    E e = ((C)(x)).GetEnumerator(); 
    try { 
     while (e.MoveNext()) { 
      V v = (V)(T)e.Current; 
      embedded-statement 
     } 
    } 
    finally { 
     … // Dispose e 
    } 
} 

所以給你的e xplicit投射到IEnumerable,你將結束與以下內容:

  • C = System.Collections.IEnumerable
  • X = new MyIntegers() as System.Collections.IEnumerable
  • E = System.Collections.IEnumerator
  • T = System.Object
  • V = System.Object
{ 
    System.Collections.IEnumerator e = ((System.Collections.IEnumerable)(new MyIntegers() as System.Collections.IEnumerable)).GetEnumerator(); 
    try { 
     while (e.MoveNext()) { 
      System.Object element = (System.Object)(System.Object)e.Current; 
      Console.WriteLine(element); 
     } 
    } 
    finally { 
     System.IDisposable d = e as System.IDisposable; 
     if (d != null) d.Dispose(); 
    } 
} 

這解釋了爲什麼使用演員陣容。

+1

什麼錯誤?代碼無需公共'Current'即可編譯http://i.imgur.com/gxqaEeq.png –

+0

當您將它轉換爲IEnumerable時,它會進行編譯,但這是因爲您明確使用了「IEnumerable」類型,該類型滿足(IEnumerable.GetEnumerator'返回一個具有公共'MoveNext()'和'Current'成員的IEnumerator')。 – Lukazoid

+0

好,讓我明白請。你是說編譯器將它翻譯成**第二**這裏 - http://i.imgur.com/IfmLD5B.png –