c# infinite-loop enumerator

c# - Extraño comportamiento de Enumerator.MoveNext()



infinite-loop (2)

¿Podría alguien explicar por qué este código se ejecuta en un bucle infinito? ¿Por qué MoveNext() devuelve true siempre?

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


Mientras que el constructo foreach en C # y el bucle For Each en VB.NET a menudo se usan con tipos que implementan IEnumerable<T> , aceptarán cualquier tipo que incluya un método GetEnumerator cuyo tipo de devolución proporcione una función MoveNext adecuada y la propiedad Current . El hecho de que GetEnumerator devuelva un tipo de valor permitirá en muchos casos que foreach se implemente más eficientemente de lo que sería posible si devolviera IEnumerator<T> .

Desafortunadamente, debido a que no existe un medio por el cual un tipo pueda suministrar un enumerador de tipo de valor cuando se invoca desde foreach sin proporcionar uno cuando se invoca mediante una llamada al método GetEnumerator , los autores de List<T> enfrentaron a una ligera compensación de rendimiento versus semántica. En ese momento, como C # no era compatible con la inferencia de tipo variable, cualquier código que utilizara el valor devuelto por List<T>.GetEnumerator tendría que declarar una variable de tipo IEnumerator<T> o List<T>.Enumerator . El código que utiliza el primer tipo se comportaría como si List<T>.Enumerator fuera un tipo de referencia, y un programador que use este último podría suponer que se dio cuenta de que era un tipo de estructura. Sin embargo, cuando C # agregó una inferencia de tipo, esa suposición dejó de mantenerse. El código podría terminar fácilmente usando el tipo List<T>.Enumerator sin que el programador sepa de la existencia de ese tipo.

Si C # alguna vez definiera un atributo struct-method que se pudiera usar para etiquetar métodos que no deberían ser invoables en estructuras de solo lectura, y si List<T>.Enumerator lo usara, un código como el suyo podría dar como resultado un error en tiempo de compilación en la llamada a MoveNext lugar de eso produciendo un comportamiento falso. Sin embargo, no conozco ningún plan particular para agregar tal atributo.


List<T>.GetEnumerator() devuelve un tipo de valor mutable ( List<T>.Enumerator ). Está almacenando ese valor en el tipo anónimo.

Ahora, echemos un vistazo a lo que hace esto:

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

Eso es equivalente a:

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

Ahora tenga en cuenta lo que estamos llamando MoveNext() en - la copia del valor que está en el tipo anónimo. En realidad, no puede cambiar el valor en el tipo anónimo: todo lo que tiene es una propiedad a la que puede llamar, que le dará una copia del valor.

Si cambias el código a:

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

... entonces terminarás obteniendo una referencia en el tipo anónimo. Una referencia a un cuadro que contiene el valor mutable. Cuando llames a MoveNext() en esa referencia, el valor dentro del cuadro se mutará, por lo que hará lo que quieras.

Para el análisis de una situación muy similar (otra vez usando List<T>.GetEnumerator() ) vea mi publicación de blog de 2010 "¡Iterate, maldito seas!" .