¿Por qué la Variable de iteración en una instrucción de C # foreach es de solo lectura?

Según lo entiendo, la variable de iteración foreach de C # es inmutable.

Lo que significa que no puedo modificar el iterador de esta manera:

foreach (Position Location in Map) { //We want to fudge the position to hide the exact coordinates Location = Location + Random(); //Compiler Error Plot(Location); } 

No puedo modificar la variable del iterador directamente y en su lugar, tengo que usar un bucle for

 for (int i = 0; i < Map.Count; i++) { Position Location = Map[i]; Location = Location + Random(); Plot(Location); i = Location; } 

Viniendo de un fondo C ++, veo foreach como una alternativa al bucle for. Pero con la restricción anterior, suelo recurrir al uso del bucle for.

Tengo curiosidad, ¿cuál es la razón detrás de hacer que el iterador sea inmutable?


Editar:

Esta pregunta es más una cuestión de curiosidad y no una cuestión de encoding. Aprecié las respuestas de encoding, pero no puedo marcarlas como respuestas.

Además, el ejemplo anterior fue simplificado en exceso. Aquí hay un ejemplo de C ++ de lo que quiero hacer:

 // The game's rules: // - The "Laser Of Death (tm)" moves around the game board from the // start area (index 0) until the end area (index BoardSize) // - If the Laser hits a teleporter, destroy that teleporter on the // board and move the Laser to the square where the teleporter // points to // - If the Laser hits a player, deal 15 damage and stop the laser. for (int i = 0; i < BoardSize; i++) { if (GetItem(Board[i]) == Teleporter) { TeleportSquare = GetTeleportSquare(Board[i]); SetItem(Board[i], FreeSpace); i = TeleportSquare; } if (GetItem(Board[i]) == Player) { Player.Life -= 15; break; } } 

No puedo hacer lo anterior en el foreach de C # porque el iterador i es inmutable. Creo (corríjanme si me equivoco), esto es específico del diseño de foreach en los idiomas.

Me interesa saber por qué el iterador foreach es inmutable.

Comencemos con un ejemplo tonto pero ilustrativo:

 Object o = 15; o = "apples"; 

En ningún momento tenemos la impresión de que simplemente convertimos el número 15 en una cadena de manzanas. Sabemos que o es simplemente un puntero. Ahora hagamos esto en forma de iterador.

 int[] nums = { 15, 16, 17 }; foreach (Object o in nums) { o = "apples"; } 

Nuevamente, esto realmente no logra nada. O al menos no lograría nada si comstackra. Ciertamente no insertaría nuestra cadena en la matriz int, eso no está permitido, y sabemos que o es sólo un puntero de todos modos.

Tomemos su ejemplo:

 foreach (Position Location in Map) { //We want to fudge the position to hide the exact coordinates Location = Location + Random(); //Compiler Error Plot(Location); } 

Si esto se comstackra, la Location en su ejemplo titila refiriéndose a un valor en Map , pero luego la cambia para referirse a una nueva Position (implícitamente creada por el operador de adición). Funcionalmente es equivalente a esto (que SÍ comstack):

 foreach (Position Location in Map) { //We want to fudge the position to hide the exact coordinates Position Location2 = Location + Random(); //No more Error Plot(Location2); } 

Entonces, ¿por qué Microsoft le prohíbe volver a asignar el puntero utilizado para la iteración? Claridad para una cosa: no quiere que las personas le asignen pensando que han cambiado su posición dentro del ciclo. Facilidad de implementación para otro: la variable podría ocultar alguna lógica interna que indique el estado del ciclo en progreso.

Pero más importante aún, no hay ninguna razón para que quiera asignarle. Representa el elemento actual de la secuencia de bucle. Asignarle un valor rompe el “Principio de Responsabilidad Individual” o la Ley de Curly si sigues Coding Horror. Una variable debe significar una sola cosa.

Si la variable era mutable, eso podría dar una impresión incorrecta. Por ejemplo:

 string[] names = { "Jon", "Holly", "Tom", "Robin", "William" }; foreach (string name in names) { name = name + " Skeet"; } 

Algunas personas podrían pensar que eso cambiaría el contenido de la matriz. Está llegando un poco, pero podría ser una razón. Lo buscaré en mi especificación anotada esta noche …

Creo que es una limitación artificial, realmente no es necesario hacerlo. Para probar el punto, tome este código en consideración y una posible solución a su problema. El problema es asignar, pero los cambios internos de los objetos no presentan un problema:

 using System; using System.Collections.Generic; namespace ConsoleApplication1 { class Program { static void Main(string[] args) { List colection =new List{ new MyObject{ id=1, Name="obj1" }, new MyObject{ id=2, Name="obj2"} }; foreach (MyObject b in colection) { // b += 3; //Doesn't work b.Add(3); //Works } } class MyObject { public static MyObject operator +(MyObject b1, int b2) { return new MyObject { id = b1.id + b2, Name = b1.Name }; } public void Add(int b2) { this.id += b2; } public string Name; public int id; } } } 

No sabía acerca de este fenómeno, porque siempre estaba modificando los objetos de la manera que describí.

La statement de foreach funciona con Enumerable. Lo que es diferente con un Enumerable es que no sabe acerca de la colección como un todo, es casi ciego.

Esto significa que no sabe qué viene a continuación, o incluso si hay algo hasta el siguiente ciclo de iteración. Es por eso que ves .ToList () mucho para que la gente pueda contar el Enumerable.

Por alguna razón que no estoy seguro, esto significa que debe asegurarse de que su colección no cambie a medida que intenta mover el enumerador.

Cuando modifica la colección, la modificación puede tener efectos secundarios impredecibles. El encuestador no tiene manera de saber cómo tratar correctamente estos efectos secundarios, por lo que hicieron las colecciones inmutables.

Vea también esta pregunta: ¿Cuál es la mejor forma de modificar una lista en un foreach?

La condición para trabajar con objetos IEnumerable es que la colección subyacente no debe cambiar mientras está accediendo a ella con Enumerable. Puede suponer que el objeto Enumerable es una instantánea de la colección original. Entonces, si intentas cambiar la colección mientras la enumeras, lanzará una excepción. Sin embargo, los objetos recuperados en Enumeration no son inmutables.

Como la variable utilizada en el bucle foreach es local al bloque de bucle, esta variable no está disponible fuera del bloque.