Como yo lo entiendo, C#foreach variable de iteración es inmutable.

Que significa que no puede modificar el iterador como este:

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

     Plot(Location);
}

Yo no puedo modificar la variable de iteración 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 C++ fondo, veo foreach como una alternativa para el bucle for. Pero con la restricción anterior, yo usualmente se reserva para el uso del bucle for.

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


Edición:

Esta pregunta es más una curiosidad pregunta y no como una codificación de la pregunta. Me gustó la codificación de las respuestas pero no puedo marcarlos como respuestas.

También, el ejemplo anterior se sobre-simplificado. Aquí es 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;
    }
}

Yo no puedo hacer lo anterior en C#foreach porque el iterador i es inmutable. Creo (corrígeme si estoy equivocado), esto es específico para el diseño de un foreach en idiomas.

Estoy interesado en por qué el foreach iterador es inmutable.

  • Buen hallazgo. Estoy un poco sorprendido de que yo nunca he notado que antes.
  • Usted no está realmente cambiando el elemento de la matriz en el segundo ejemplo, bien.
  • No estoy realmente interesado en cambiar el elemento de la matriz. Estoy más interesado en saber por qué el iterador es inmutable. Creo que es necesario editar la pregunta para aclarar esto.
  • Acepto la corrección, mi 2º ejemplo faltaba una línea. Gracias por el aviso.
InformationsquelleAutor MrValdez | 2009-04-22

6 Comentarios

  1. 25

    Empecemos con un pequeño pero ilustrativo ejemplo:

    Object o = 15;
    o = "apples";

    En ningún momento tenemos la impresión de que nos acaba de cumplir el número 15 en una cadena de manzanas. Sabemos que o es simplemente un puntero. Ahora vamos a hacer esto en iterador forma.

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

    De nuevo, esto realmente no logra nada. O al menos es sería lograr nada fueron a compilar. Ciertamente no insertar nuestra cadena en la matriz int — que no está permitido, y sabemos que o es simplemente 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);
    }

    Se esta a compilar, el Location en su ejemplo, estrellas de referencia a un valor en Map, pero, a continuación, cambiar para referirse a un nuevo Position (implícitamente creado por el operador de adición). Funcionalmente es equivalente a este (que compila):

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

    Así que, ¿por qué Microsoft prohibir el re-asignar el puntero utilizado para la iteración? Claridad, por una sola cosa: no quieren que la gente asignar a pensar que se ha cambiado su posición dentro del bucle. Facilidad de implementación para otro: La variable puede ocultar cierta lógica interna que indica el estado del bucle en el progreso.

    Pero lo que es más importante, no hay ninguna razón para que usted quiere para asignar a la misma. Representa el elemento actual de la secuencia de bucle. La asignación de un valor para el que se rompe el «Principio de Responsabilidad Único» o Rizado de la Ley si usted sigue la Codificación de Horror. Una variable debe significar sólo una cosa.

    • Tyler, puedo estar equivocado, pero no estoy de acuerdo con tu afirmación de que «la Posición de Ubicación» o «Objeto» son iteradores. En su lugar, se iteración variables que se han iteradores que operan en ellos. Si ese es el caso, entonces creo que su razonamiento puede ser un poco sesgada. Estoy de acuerdo en que al cambiar el valor de la variable de iteración puede causar claridad los problemas, pero me gustaría que Microsoft había dejado que el desarrollador decida. He encontrado un número de casos donde habría sido útil para cambiar el valor de un foreach variable de iteración.
    • No estoy de acuerdo en que se debe dejar que el desarrollador de decidir en este caso. Hay un costo real para confuso código, y Microsoft tiene suficiente gente codificación en C# que tiene un verdadero incentivo para la creación de un lenguaje que, tanto como sea posible, evita que el desarrollador de la creación de errores.
    • «Una variable debe significar sólo una cosa«. Buen concepto. En mi cabeza, sin embargo, sería mejor si simplemente significaba «elemento» en lugar de «referencia» o «puntero«. O, en el op instancia Map[i], que es exactamente lo que intuitivamente esperar.
    • Yo también no está de acuerdo con usted. C#’s foreach mata a todo el concepto de mutable de los iteradores. Me veo obligado a hacer una copia, mientras que la iteración con foreach y asignar a la copia después, lo que es, de hecho, ineficient. O a abandonar el uso de la sintaxis de azúcar como foreach en favor de la buena edad para(i;i<n;i++) bucles para este propósito. En C++, nuestra gama basada en for(auto& item: collection) nos permite hacer mutable de los iteradores. La única solución es hacer reiterado tema de las clases y el uso de la mutación métodos sobre ellos. Definitivamente no es divertido si queremos mutar primitivas.
    • foreach (Object o in nums) { , ¿no es básicamente el mismo como la fundición de un List<T> en IList? Eso es perfectamente factible. Sin embargo, la asignación arbitraria object a través de IList‘s indizador no es posible: Si el valor asignado no coincide con el establecimiento inflexible de tipos, tipo de elemento de lista, se producirá una excepción. o = "apples"; podría fácilmente ser tratados de la misma manera.
    • Aparte de eso, para «usted no quiere a la gente de la asignación a la misma de pensamiento que han cambiado su posición dentro del bucle» – como he señalado en otro comentario aquí, yo actualmente no ve ninguna razón técnica por la variable de iteración en un foreach bucle no podía trabajar así.

  2. 14

    Si la variable se mutable, que 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ía pensar que iba a cambiar el contenido de la matriz. Está llegando un poco, pero podría ser una razón. Voy a mirar en mi anotado especificaciones de esta noche…

    • Ese sería el punto de Jon está tratando de ilustrar supongo. Observe el «Si».
    • Brian, fue su comentario en respuesta a otro comentario que ahora se ha eliminado?
    • ¿Alguna vez has visto esto en tu spec? Supongo que los diseñadores tomaron el valor de sólo lectura para mayor claridad como usted sugiere, pero sería interesante saber si existen otras consideraciones!
    • Es esto a usted y a su familia, Jon?
    • «Algunas personas que creo que iba a cambiar el contenido de la matriz.» – No estoy seguro de cuál es el problema aquí. Obviamente, el compilador podría compilar acceso a lo que aparece como una simple variable de iteración en el código fuente en algo así como una propiedad de captador/definidor de la llamada, que puede modificar la matriz subyacente como sea necesario.
    • Sería muy sorprendente comportamiento, de la OMI. Y aunque se podría trabajar para las matrices, no podía trabajar para otras formas de foreach, que conduce a un muy extraño diseño de la OMI. Me alegro de que así es.
    • sería muy sorprendente comportamiento, de la OMI.» – dado que no es de extrañar cuando las propiedades se comportan de la misma manera, nuestras opiniones parecen diferir en eso. En particular, no estoy convencido de que no ser capaz de asignar a la variable de iteración es menos sorprendente que aparentemente simple de las situaciones (como cuando se itera sobre una matriz). «Y mientras que se podría trabajar para las matrices, no podía trabajar para otras formas de foreach» – el compilador permite foreach en las cosas que implementan IEnumerable. No veo ninguna razón por qué no puede también permite la asignación a la variable de iteración dentro de …
    • foreach al otro, condición similar (por ejemplo, un IModifyableEnumerable interfaz) se cumple. En última instancia, sin embargo, uno se acostumbra a cualquier manera que se define como el tiempo de una manera en que funciona el lenguaje.
    • Propiedades y variables locales actuar de manera muy diferente en todo tipo de formas. Usted está sugiriendo que algo debe ser cambiado para hacer un determinado tipo de variable local se comportan de manera diferente a los demás. Tenga en cuenta que la «sorpresa» de no ser capaz de asignar a la variable de iteración conduce a un error en tiempo de compilación, mientras que la sorpresa de que la compilación se comporta de manera diferente a las expectativas de haría (OMI) conducen a pequeños errores.
    • hacer una clase particular de variable local se comportan de manera diferente a los demás» – ya no se comportan de manera diferente a los demás. No puede ser asignado. Podría decirse que estoy sugiriendo hacer este tipo especial de variable local se comportan más de manera similar a los demás.
    • Pero eso es más seguro y más simple diferencia de lo que usted se propone. De todos modos, no creo que ninguno de nosotros se va a persuadir a la otra. Yo apoyo firmemente el lenguaje de diseño aquí.
    • Con ref variables locales en C# 7, puedo ver un caso de uso para una variable de iteración para ser declarado como ref, así como para las matrices. Que sería razonable – pero no acaba de hacerlo, implícitamente, de la OMI.

  3. 10

    Creo que su limitación artificial, no hay realmente necesidad de hacerlo. Para probar el punto, tomar este código en considiration, y una posible solución a su problema. El problema es la señal, 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<MyObject> colection =new List<MyObject>{ 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;
            }
        }
    }

    Yo no sabía acerca de este fenómeno, porque siempre he sido la modificación de los objetos de la manera que he descrito.

    • +1 para un trabajo decente a su alrededor.
  4. 2

    La instrucción foreach trabaja con Enumerable. Lo que es diferente con un Enumerable es que no saben acerca de la colección como un todo, es casi ciego.

    Esto significa que no sabe lo que viene después, o el hecho de que si no hay nada hasta la siguiente iteración del ciclo. Que por qué hay que ver .ToList() mucho para que la gente pueda agarrar un recuento de la Enumerable.

    Por alguna razón que no estoy seguro, esto significa que usted necesita para asegurarse de que su colección no está cambiando a medida que tratas de mover el enumerador a lo largo.

    • Pero el cambio de la variable no cambia el enumerador de todos modos. Es sólo una copia local del valor actual.
    • Sin duda, esto depende de si su referencia o el tipo de valor? Pero sí, yo entiendo que usted está buscando en el valor, y tal vez no se ajuste del enumerador de la misma… hmm
    • Seguramente, esto no es un «duro» de la razón para no permitir la modificación de la variable de iteración. El compilador ya está codificada para buscar IEnumerable/IEnumerable<T> para decidir si permitir o no que un foreach bucle en el primer lugar. Si el lenguaje de los diseñadores habían elegido para permitir la modificación de la variable de iteración, el compilador podría haber sido la aceptación de tales modificaciones en los casos donde la foreach bucle se utiliza para iterar a través de una IList/IList<T>.
  5. 2

    Cuando se modifica la colección, la modificación puede tener efectos colaterales impredecibles. El enumerador no tiene manera de saber cómo tratar correctamente con estos efectos secundarios, por lo que han hecho las colecciones inmutable.

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

    • Puede usted nombrar al menos un ejemplo concreto de un efecto secundario que el enumerador tendría que manejar (que no puede ocurrir cuando la variable de iteración es inmutable)? De lo contrario, esta respuesta suena muy vago.
  6. 1

    La condición para trabajar con IEnumerable objetos es que la colección no puede cambiar mientras se accede a ella con Enumerable. Usted puede suponer que el objeto Enumerable es una captura de la colección original. Así que si usted intenta cambiar la colección, mientras que la enumeración que va a lanzar una excepción. Sin embargo, la tomó de los objetos en la Enumeración no es inmutable en todo.

    Ya que la variable que se utiliza en bucle foreach es local en el bloque de loop esta variable es, sin embargo, no se dispone de fuera del bloque.

Dejar respuesta

Please enter your comment!
Please enter your name here