2011-10-20 30 views
30

Tenía curiosidad acerca de esto: el siguiente código no se compilará, porque no se puede modificar una variable foreach iteración:¿Por qué no podemos asignar una variable de iteración foreach, mientras que podemos modificarla completamente con un descriptor de acceso?

 foreach (var item in MyObjectList) 
     { 
      item = Value; 
     } 

pero el siguiente será compilar y ejecutar:

 foreach (var item in MyObjectList) 
     { 
      item.Value = Value; 
     } 

¿Por qué es el primer inválido, mientras que el segundo puede hacer lo mismo debajo (estaba buscando la expresión correcta en inglés para esto, pero no lo recuerdo. ¿Bajo el ...? ^^)

+5

Creo que significa "debajo del capó", pero en realidad no es lo mismo en absoluto. –

+0

@ GianT971 ¿Podría describir cómo haría 'lo mismo' en su setter? 'this = value'? – jv42

+0

@JonSkeet Yess Under the hood ^^. Ok, el elemento es en realidad una referencia al objeto, y no el objeto en sí, lo tengo ahora – GianT971

Respuesta

27

foreach es una de sólo lectura iterador que itera dinámicamente clases que implementan IEnumerable, cada ciclo de foreach llamarán al IEnumerable para obtener el siguiente artículo, el artículo que tiene es de referencia de solo lectura, no puede volver a asignarlo, simplemente llamando al item.Value está accediendo a él y asignando algún valor a un atributo de lectura/escritura pero sigue siendo la referencia del elemento una lectura solo referencia.

+2

"_foreach es un iterador de solo lectura que repite dinámicamente las clases que implementan IEnumerable_" Si bien es correcto, esto no es estrictamente cierto.El compilador solo requiere el tipo que está iterando para tener los métodos 'T GetEnumerator()', 'bool MoveNext()' y una propiedad 'T Current' (suponiendo que desee crear su propio enumerador). En realidad no necesitas implementar 'IEnumerable'. –

24

Lo segundo no está haciendo lo mismo en absoluto. No está cambiando el valor de la variable item; está cambiando una propiedad del objeto al que se refiere ese valor. Estos dos serían solo equivalentes si item es un tipo de valor variable, en cuyo caso debe cambiarlo de todos modos, ya que los tipos de valores variables son malos. (Se comportan en todo tipo de formas, que el desarrollador desprevenido puede no esperar.)

Es lo mismo que esto:

private readonly StringBuilder builder = new StringBuilder(); 

// Later... 
builder = null; // Not allowed - you can't change the *variable* 

// Allowed - changes the contents of the *object* to which the value 
// of builder refers. 
builder.Append("Foo"); 

Ver mi article on references and values para más información.

2

Porque la primera no tiene mucho sentido, básicamente. El elemento variable está controlado por el iterador (establecido en cada iteración). No debería ser necesario cambiar IT sólo tiene que utilizar otra variable:

foreach (var item in MyObjectList) 
{ 
    var someOtherItem = Value; 

    .... 
} 

En cuanto a la segunda, hay casos de uso válidos allí- es posible que desee iterar sobre una enumeración de los coches y llame .Drive() en cada uno, o establecer car.gasTank = full;

0

El punto es que no se puede modificar la propia colección al iterar sobre ella. Es absolutamente legal y común modificar los objetos que produce el iterador.

1

Porque los dos no son los mismos. Al hacer lo primero, puede esperar cambiar el valor que está en la colección. Pero de la forma en que funciona el foreach, no hay forma de que se haya podido hacer. Solo puede recuperar elementos de la colección, no establecerlos.

Pero una vez que tiene el elemento, es un objeto como cualquier otro y puede modificarlo de cualquier forma (permitida) que pueda.

10

Si nos fijamos en la especificación del lenguaje se puede ver por qué esto no está funcionando:

Las especificaciones dicen que un foreach se expande al siguiente código:

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

Como se puede ver la corriente elemento se usa para llamar a MoveNext() uno. Entonces, si cambia el elemento actual, el código se 'pierde' y no puede iterar sobre la colección. Así que cambiar el elemento a otra cosa no tiene ningún sentido si se ve qué código está produciendo el compilador.

+2

Como entiendo, "v" mantendrá la referencia del elemento actual y "e" contiene la referencia del iterador. Entonces, el compilador podría simplemente asignar lo que quisiéramos o 'v' y' e' todavía mantendría el iterador. De esta forma, las variables del elemento foreach pueden asignarse nuevas referencias, ya que se convierten a la variable 'v', no a' e'. – mFeinstein

10

No se puede modificar una colección mientras se está enumerando. El segundo ejemplo solo actualiza una propiedad del objeto, que es completamente diferente.

utilizar un bucle for si necesita añadir/eliminar/modificar los elementos de una colección:

for (int i = 0; i < MyObjectList.Count; i++) 
{ 
    MyObjectList[i] = new MyObject(); 
} 
2

Es sería ser posible hacer item mutable. Podríamos cambiar la forma en que el código se produce de manera que:

foreach (var item in MyObjectList) 
{ 
    item = Value; 
} 

Se convirtió en equivalente a:

using(var enumerator = MyObjectList.GetEnumerator()) 
{ 
    while(enumerator.MoveNext()) 
    { 
    var item = enumerator.Current; 
    item = Value; 
    } 
} 

y sería entonces compilar. Sin embargo, no afectaría la colección.

Y ahí está el problema. El código:

foreach (var item in MyObjectList) 
{ 
    item = Value; 
} 

Tiene dos formas razonables para que un ser humano lo piense. Una es que item es sólo un lugar de titular y cambio que no es diferente a los cambios en item:

for(int item = 0; item < 100; item++) 
    item *= 2; //perfectly valid 

La otra es que el cambio de tema sería realmente cambiar la colección.

En el primer caso, podemos simplemente asignar un elemento a otra variable, y luego jugar con eso, para que no haya pérdidas. En el último caso, esto está prohibido (o al menos, no puede esperar que altere una colección mientras se itera a través de ella, aunque tiene impuesta por todos los enumeradores) y en muchos casos es imposible proporcionar (dependiendo de la naturaleza del enumerable).

Incluso si consideramos que el primer caso es la implementación "correcta", el hecho de que pueda ser razonablemente interpretado por humanos de dos maneras diferentes es una razón suficiente para evitarlo, especialmente si tenemos en cuenta que podemos trabajar fácilmente eso en cualquier caso.

1

Úselo para el bucle en lugar del bucle foreach y asigne valor. funcionará

foreach (var a in FixValue) 
{ 
    for (int i = 0; i < str.Count(); i++) 
    { 
     if (a.Value.Contains(str[i])) 
      str[i] = a.Key; 
    } 
} 
Cuestiones relacionadas