2015-07-23 21 views
34

Qualcuno potrebbe spiegare perché questo codice è in esecuzione nel ciclo infinito? Perché MoveNext() restituisce sempre true?Strano comportamento di Enumerator.MoveNext()

var x = new { TempList = new List<int> { 1, 3, 6, 9 }.GetEnumerator() }; 
while (x.TempList.MoveNext()) 
{ 
    Console.WriteLine("Hello World"); 
} 

risposta

40

List<T>.GetEnumerator() restituisce un tipo di valore mutabile (List<T>.Enumerator). Stai memorizzando quel valore nel tipo anonimo.

Ora, diamo un'occhiata a ciò che fa:

while (x.TempList.MoveNext()) 
{ 
    // Ignore this 
} 

Questo è equivalente a:

while (true) 
{ 
    var tmp = x.TempList; 
    var result = tmp.MoveNext(); 
    if (!result) 
    { 
     break; 
    } 

    // Original loop body 
} 

Ora notare quello che stiamo chiamando MoveNext() sul - la copia del valore che è nel tipo anonimo. Non puoi in realtà cambiare il valore nel tipo anonimo: tutto ciò che hai è una proprietà che puoi chiamare, che ti darà una copia del valore.

Se si modifica il codice per:

var x = new { TempList = (IEnumerable<int>) new List<int> { 1, 3, 6, 9 }.GetEnumerator() }; 

... Allora ti finisce per ottenere un di riferimento nel tipo anonimo. Un riferimento a una scatola contenente il valore mutabile. Quando chiami MoveNext() su quel riferimento, il valore all'interno della casella verrà modificato, quindi farà ciò che desideri.

Per analisi su una situazione molto simile (sempre utilizzando List<T>.GetEnumerator()), vedere my 2010 blog post "Iterate, damn you!".

+3

Sto guardando questo e ho difficoltà a capire quale elemento qui è un tipo di valore per il quale la distinzione copia/riferimento effettivamente fa la differenza. –

+1

non ce l'ho, come funziona bene se lo lanciamo in '' IEnumerable '' ?? @JonSkeet puoi spiegarlo di più con parole semplici –

+1

@EhsanSajjad: Quando il tipo in fase di compilazione di 'TempList' è' IEnumerable ', l'accesso ad esso copia semplicemente un riferimento. Agire su quel riferimento muterà il valore in box, e la volta successiva che copi un riferimento si riferirà comunque allo stesso valore in box, quindi vedrai la modifica. Confrontalo con il valore del tipo di valore e modificando la copia - quando * successivo * copi il valore originale, non vedrai la modifica precedente. –

3

Mentre il foreach costrutto in C# e il ciclo For Each in VB.NET sono spesso utilizzati con i tipi che implementano IEnumerable<T>, essi accettare qualsiasi tipo che include un metodo GetEnumerator cui tipo restituito fornisce un adeguato MoveNext funzione e Current struttura. Avere un valore di tipo GetEnumerator in molti casi consente di implementare foreach in modo più efficiente di quanto sarebbe possibile se restituisse IEnumerator<T>.

Purtroppo, perché non ci sono mezzi con cui un tipo può fornire un valore di tipo enumeratore quando viene richiamato da foreach senza fornire anche uno quando viene richiamato da una chiamata GetEnumerator metodo, di List<T> gli autori hanno affrontato un leggero trade-off di prestazioni contro semantica. Al momento, poiché C# non supportava l'inferenza di tipo variabile, qualsiasi codice che utilizzasse il valore restituito da List<T>.GetEnumerator avrebbe dovuto dichiarare una variabile di tipo IEnumerator<T> o List<T>.Enumerator. Il codice che utilizza il tipo precedente si comporterebbe come se List<T>.Enumerator fosse un tipo di riferimento e un programmatore che utilizza quest'ultimo potrebbe presumersi che fosse un tipo di struttura. Quando C# aggiungeva inferenza di tipo, tuttavia, quell'assunzione cessava di valere. Il codice potrebbe facilmente finire con il tipo List<T>.Enumerator senza che il programmatore sappia dell'esistenza di quel tipo.

Se C# dovesse mai definire un attributo struct-method che potrebbe essere utilizzato per taggare metodi che non dovrebbero essere richiamabili su strutture di sola lettura, e se List<T>.Enumerator ne ha fatto uso, codice come il proprio potrebbe restituire correttamente un errore in fase di compilazione sulla chiamata a MoveNext piuttosto che produrre un comportamento fasullo. Non conosco piani particolari per aggiungere un tale attributo, comunque.