web-dev-qa-db-fra.com

Pourquoi ne pouvons-nous pas affecter une variable d'itération foreach, alors que nous pouvons la modifier complètement avec un accesseur?

J'étais juste curieux à ce sujet: le code suivant ne se compilera pas, car nous ne pouvons pas modifier une variable d'itération foreach:

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

Mais ce qui suit se compile et s'exécute:

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

Pourquoi le premier est-il invalide, alors que le second peut faire la même chose en dessous (je cherchais l'expression anglaise correcte pour cela, mais je ne m'en souviens pas. Sous le ...? ^^)

46
GianT971

foreach est un itérateur en lecture seule qui itère dynamiquement les classes qui implémentent IEnumerable, chaque cycle dans foreach appellera IEnumerable pour obtenir l'élément suivant, l'élément que vous avez est une référence en lecture seule, vous ne pouvez pas le réaffecter, mais simplement en appelant item.Value y accède et attribue une valeur à un attribut de lecture/écriture tout en restant la référence de l'élément une référence en lecture seule.

35
Mohamed Abed

Le second ne fait pas du tout la même chose. Il ne s'agit pas de modifier la valeur de la variable item, mais de modifier une propriété de l'objet auquel cette valeur se réfère. Ces deux seraient seulement équivalents si item est un type de valeur mutable - auquel cas vous devriez le changer quand même, comme types de valeur mutable sont mauvais. (Ils se comportent de toutes sortes de manières auxquelles le développeur imprudent ne peut pas s'attendre.)

C'est la même chose que ceci:

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

Voir mon article sur les références et les valeurs pour plus d'informations.

33
Jon Skeet

Vous ne pouvez pas modifier une collection pendant son énumération. Le deuxième exemple met uniquement à jour une propriété de l'objet, qui est entièrement différente.

Utilisez une boucle for si vous devez ajouter/supprimer/modifier des éléments dans une collection:

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

Si vous regardez la spécification de la langue, vous pouvez voir pourquoi cela ne fonctionne pas:

Les spécifications indiquent qu'une foreach est étendue au code suivant:

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

Comme vous pouvez le voir, l'élément courant est utilisé pour appeler MoveNext () un. Donc, si vous modifiez l'élément actuel, le code est "perdu" et ne peut pas parcourir la collection. Donc, changer l'élément en quelque chose d'autre n'a aucun sens si vous voyez quel code le compilateur produit réellement.

11
Wouter de Kort

Il serait possible de rendre item mutable. Nous pourrions changer la façon dont le code est produit afin que:

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

Devenu équivalent à:

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

Et il compilerait alors. Cela n'affecterait cependant pas la collection.

Et il y a le hic. Le code:

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

A deux façons raisonnables pour un humain d'y penser. La première est que item est juste un espace réservé et le changer n'est pas différent de changer item dans:

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

L'autre est que changer d'élément changerait en fait la collection.

Dans le premier cas, nous pouvons simplement assigner un élément à une autre variable, puis jouer avec cela, donc il n'y a pas de perte. Dans ce dernier cas, cela est à la fois interdit (ou du moins, vous ne pouvez pas vous attendre à ce que modifie une collection tout en itérant à travers elle, bien qu'elle ne le fasse pas doivent être appliqués par tous les énumérateurs) et dans de nombreux cas impossibles à fournir (selon la nature de l'énumérable).

Même si nous considérions que le premier cas était la mise en œuvre "correcte", le fait qu'il puisse être raisonnablement interprété par les humains de deux manières différentes est une raison suffisante pour éviter de le permettre, d'autant plus que nous pouvons facilement contourner cela dans tous les cas. .

4
Jon Hanna

Parce que le premier n'a pas beaucoup de sens, au fond. L'élément variable est contrôlé par l'itérateur (défini à chaque itération). Vous ne devriez pas avoir besoin de le changer - utilisez simplement une autre variable:

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

    ....
}

Comme pour le second, il existe des cas d'utilisation valides - vous pouvez vouloir parcourir une énumération de voitures et appeler .Drive () sur chacune, ou définir car.gasTank = full;

2
Chris Shain

Le fait est que vous ne pouvez pas modifier la collection elle-même tout en itérant dessus. Il est absolument légal et courant de modifier les objets générés par l'itérateur.

1
Florian Greinacher

Parce que les deux ne sont pas les mêmes. Lors de la première, vous pouvez vous attendre à modifier la valeur qui se trouve dans la collection. Mais la façon dont foreach fonctionne, il n'y a aucun moyen qui aurait pu être fait. Il peut uniquement récupérer des éléments de la collection, pas les définir.

Mais une fois que vous avez l'élément, c'est un objet comme un autre et vous pouvez le modifier de n'importe quelle manière (autorisée).

1
svick

Utilisez la boucle for au lieu de la boucle foreach et attribuez une valeur. ça va marcher

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