2015-07-10 16 views
12

ho letto l'articolo di Eric here circa foreach enumerazione e sui diversi scenari in cui foreach può funzionareC# `comportamento foreach` - Chiarimento?

Al fine di evitare che la vecchia C# versione di fare la boxe, il team di C# abilitato tipizzazione anatra per foreach per l'esecuzione su .. una collezione non IEnumerable (un pubblico GetEnumerator che restituiscono qualcosa che ha pubblica MoveNext e Current proprietà è sufficiente (

Così, Eric ha scritto un 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(); } 
} 

Ma credo che abbia alcuni errori di battitura (manca la funzione di accesso all'applicazione Current) che impedisce la compilazione (l'ho già inviato via email).

Comunque qui è una versione funzionante:

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(); } 
} 

Ok.

Secondo MSDN:

Un tipo C si dice che sia un collection type se implementa l'interfaccia System.Collections.IEnumerableo attua la collection pattern incontrando tutti i seguenti criteri:

  • C contiene un metodo di istanza pubblico con la firma GetEnumerator() che restituisce un tipo di struttura, un tipo di classe o un tipo di interfaccia, che viene chiamato E nel seguente testo.

  • E contiene un metodo di istanza pubblico con la firma MoveNext() e il tipo restituito bool.

  • E contiene una proprietà di istanza pubblica denominata Current che consente di leggere il valore corrente. Si dice che il tipo di questa proprietà sia il tipo di elemento del tipo di raccolta.

OK. Diamo abbinare i documenti di campione di Eric

campione di Eric è detto di essere un collection type perché fa implementa l'interfaccia System.Collections.IEnumerable (esplicitamente però). Ma è non (!) a collection pattern a causa di bullet 3: MyEnumerator fa non proprietà di istanza pubblica denominata Current.

MSDN dice:

Se l'espressione di raccolta è di un tipo che implementa il pattern di raccolta (come definito sopra), l'espansione della dichiarazione 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(); 
} 

Altrimenti, l'espressione di raccolta è di un tipo che implementa (!) S System.IEnumerable, e l'espansione della dichiarazione 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(); 
} 

Domanda # 1

Sembra che il campione di Eric né implementa il collection patternSystem.IEnumerable - quindi non è previsto che corrisponda a qualsiasi della condizione specificata sopra. Così come mai riesco ancora a scorrere via:

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

Domanda # 2

Perché devo menzionare new MyIntegers() as IEnumerable? è già IEnumerable (!!) e anche dopo questo, non è il compilatore sta già facendo il lavoro da sola via casting:

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

E 'proprio qui:

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

Allora perché ancora vuole che menzioni come Ienumerable? o meglio, sarebbe se Current erano pubbliche -

+1

Ora sappiamo che la domanda riguarda principalmente l'errore di digitazione "System.Numerable", inoltre accennerò che questa specifica del linguaggio avrebbe dovuto consentire al campione di eseguire il cast senza ricorrere all'utilizzo dell'interfaccia 'IEnumerable'. La specifica non menziona mai un errore quando il modello di raccolta è parzialmente implementato (che è ciò che ho indicato nella mia risposta evidenziando la specifica C# 5.0 che spiega questo errore). – Lukazoid

risposta

6

MyEnumerator non ha i metodi pubblici

Sì, ha richiesto. Tutto ciò che è richiesto è che ha:

  • A, leggibile Current proprietà pubblica
  • Un MoveNext() metodo pubblico senza argomenti di tipo ritorno bool

La mancanza di public qui è stato solo un altro errore di battitura, in fondo . Così com'è, l'esempio non fa ciò a cui è destinato (impedisce la boxe). Sta usando l'implementazione IEnumerable perché stai utilizzando new MyIntegers() as IEnumerable, quindi il tipo di espressione è IEnumerable e utilizza solo l'interfaccia in tutto.

Si sostiene che non implementa IEnumerable, (che è System.Collections.IEnumerable, btw) ma lo fa, utilizzando l'implementazione esplicita dell'interfaccia.

E 'più semplice per verificare questo genere di cose senza attuazione IEnumerable affatto:

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); 
     } 
    } 
} 

Ora, se si effettua Current privata, non si compila.

+4

La corrente non è pubblica (non sono il downvoter). –

+1

@RoyiNamir: Ah, è vero - questo è solo un altro errore allora. Come dici tu, l'esempio esistente non verrebbe compilato, inserendo "public" ed è corretto. Modificherà. –

+2

Ma funziona già tutto così senza http://i.imgur.com/gxqaEeq.png http://i.imgur.com/gxqaEeq.png –

4

Il riferimento a System.IEnumerable su MSDN non è altro che un errore di battitura in una specifica di lingua precedente, non esiste un'interfaccia di questo tipo, credo che dovrebbe riferirsi a System.Collections.IEnumerable.

Si consiglia di leggere le specifiche della lingua della versione di C# in uso, le specifiche del linguaggio C# 5.0 sono disponibili here.

Alcuni ulteriori informazioni sul motivo per cui non riuscendo a lanciare questo esempio si traduce in un errore piuttosto che di ricorrere all'invio System.Collections.IEnumerable:

Nella specifica per foreach (sezione 8.8.4) potrete vedere le regole sono leggermente modificato (alcuni passi sono stati tagliati per brevità):

  • Esegui ricerca membro del tipo X con identificativo GetEnumerator e nessun tipo argomenti. Se la ricerca del membro non produce una corrispondenza, o produce un'ambiguità, o produce una corrispondenza che non è un gruppo di metodi, controlla un'interfaccia enumerabile come descritto di seguito. È consigliabile che venga emesso un avviso se la ricerca membro produce qualcosa, tranne un gruppo di metodi o nessuna corrispondenza.
  • Eseguire la risoluzione di sovraccarico utilizzando il gruppo di metodi risultante e un elenco di argomenti vuoto. Se la risoluzione del sovraccarico non produce metodi applicabili, genera un'ambiguità o risulta in un unico metodo migliore, ma tale metodo è statico o non pubblico, verificare la presenza di un'interfaccia enumerabile come descritto di seguito. Si consiglia di inviare un avvertimento se la risoluzione di sovraccarico produce qualsiasi cosa tranne un metodo di istanza pubblica non ambiguo o nessun metodo applicabile.
  • Se il tipo di ritorno E del metodo GetEnumerator non è una classe, una struttura o un tipo di interfaccia, viene generato un errore e non vengono eseguiti ulteriori passaggi.
  • La ricerca dei membri viene eseguita su E con l'identificatore Current e nessun argomento di tipo. Se la ricerca del membro non produce corrispondenze, il risultato è un errore oppure il risultato è qualsiasi cosa, tranne una proprietà di istanza pubblica che consente la lettura, viene generato un errore e non vengono eseguiti ulteriori passaggi.
  • Il tipo di raccolta è X, il tipo di enumeratore è E e il tipo di elemento è il tipo della proprietà Corrente.

Quindi dal primo punto che avremmo trovato il public MyEnumerator GetEnumerator(), secondo e terzo proiettili passano attraverso senza errori. Quando arriviamo al quarto punto elenco, nessun membro pubblico chiamato Current è disponibile e ciò provoca l'errore che si sta verificando senza il cast, questa situazione esatta non offre mai al compilatore l'opportunità di cercare l'interfaccia enumerabile.

Quando si esegue il cast esplicito dell'istanza su uno IEnumerable, tutti i requisiti sono soddisfatti in quanto il tipo IEnumerable e l'associato IEnumerator soddisfano tutti i requisiti.

anche dalla documentazione:

I passaggi di cui sopra, in caso di successo, senza ambiguità produrre un tipo di raccolta C, tipo di enumeratore E ed elemento di tipo T. Una dichiarazione foreach della forma

foreach (V v in x) embedded-statement 

viene poi esteso a:

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

quindi, dato il tuo indirizzo e Xplicit cast IEnumerable, si finisce con il seguente:

  • 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(); 
    } 
} 

Ciò spiega perché l'utilizzo del cast funziona.

+1

Quale errore? il codice viene compilato così com'è senza 'Current' http://i.imgur.com/gxqaEeq.png –

+0

Quando lo si esegue in' IEnumerable' viene compilato, ma questo perché si utilizza esplicitamente il tipo 'IEnumerable' che soddisfa tutto quanto sopra ('IEnumerable.GetEnumerator' restituisce un' IEnumerator' che ha public 'MoveNext()' e 'Current' membri). – Lukazoid

+0

Ok fammi capire per favore. Stai dicendo che il compilatore lo traduce nel ** secondo ** qui - http://i.imgur.com/IfmLD5B.png –