2010-08-23 5 views
9

Pourquoi une boucle foreach est-elle une boucle en lecture seule? Quelles sont les raisons pour cela?Pourquoi est-ce que je ne peux pas modifier la variable de boucle dans un foreach?

+1

Je ne suis pas sûr de ce que vous entendez par une boucle en lecture seule. Faites-vous référence au fait que vous ne devriez pas modifier la variable d'itération de la boucle? I.e pour chaque article dans myitems item.property = quelque chose suivant – Tommy

+0

Je ne sais pas comment appliquer le concept de lecture seule à une boucle for. Ou voulez-vous dire pourquoi ils disent que c'est en lecture seule? – helios

+2

Si vous parlez de l'énumération - oui! Vous ne pouvez pas modifier l'énumération que vous parcourez. –

Répondre

22

Je ne sais pas exactement ce que vous entendez par une « boucle en lecture seule » mais je devine que vous voulez savoir pourquoi cela ne compile pas:

int[] ints = { 1, 2, 3 }; 
foreach (int x in ints) 
{ 
    x = 4; 
} 

Le code ci-dessus donnera les éléments suivants erreur de compilation:

 
Cannot assign to 'x' because it is a 'foreach iteration variable' 

Pourquoi cela est-il interdit? Essayer de l'assigner ne ferait probablement pas ce que vous voulez - cela ne modifierait pas le contenu de la collection originale. C'est parce que la variable x n'est pas une référence aux éléments de la liste - c'est une copie. Pour éviter que les gens écrivent du code bogué, le compilateur n'autorise pas cela.

+0

+1. C'est comme dire 'Foo x = new Foo(); Foo y = x; y = new Foo(); 'Ajuster' y' à 'new Foo()' ne fait rien à 'x'. De même, avoir 'foo' comme variable de boucle et dire' foo = new Foo(); '(si vous le pouvez **) ne fait rien pour' Foo' dans la collection. –

+1

C'est (la partie de copie) pas vrai pour le type de référence, alors x contiendrait une référence à l'objet dans le IEnumerable –

+0

@Rune FS: Dans le cas des types de référence c'est une * copie * de la référence. Même dans ce cas, il ne ferait probablement pas ce que vous vouliez si vous pouviez l'assigner. –

5

Je suppose que c'est ainsi que l'itérateur parcourt la liste.

Supposons que vous avez une liste triée:

Alaska 
Nebraska 
Ohio 

Au milieu de

foreach(var s in States) 
{ 
} 

Vous faites un States.Add ("Missouri")

Comment traitez-vous cela? Sautez-vous alors au Missouri même si vous avez déjà dépassé cet indice.

+0

Le contrat de Microsoft pour iEnumerable dit qu'un iEnumerable ne devrait pas fournir de moyen de mettre à jour la collection sans provoquer une exception quand énumérer l'élément suivant. Si j'avais mes connaissances, un enquêteur dans votre scénario serait libre d'inclure ou non le Mississippi, mais il faudrait soit énumérer exactement une fois tous les éléments qui existaient tout au long de l'énumération, soit lancer une exception s'il ne pouvait pas le faire. – supercat

3

Si, par cela, vous voulez dire:

Why shouldn't I modify the collection that's being foreach 'd over?

Il n'y a pas de caution que les articles que vous obtenez sortira dans l'ordre donné, et que l'ajout d'un élément, ou la suppression d'un article ne sera pas provoque la modification de l'ordre des éléments de la collection, ou même l'énumérateur devient invalide.

Imaginez si vous exécutiez le code suivant:

var items = GetListOfTOfSomething(); // Returns 10 items 

int i = 0; 
foreach(vat item in items) 
{ 
    i++; 
    if (i == 5) 
    { 
     items.Remove(item); 
    } 
} 

Dès que vous appuyez sur la boucle où i est 6 (à savoir après l'élément est supprimé) tout peut arriver. L'énumérateur a peut-être été invalidé parce que vous avez supprimé un élément, tout pourrait avoir été "mélangé par un" dans la collection sous-jacente, provoquant la suppression d'un élément, ce qui signifie que vous en "sautez" un.

Si vous vouliez dire « pourquoi je ne peux pas changer la valeur qui est fournie à chaque itération », puis, si la collection vous travaillez avec contient value types, les modifications apportées ne seront pas conservés comme il est un valeur avec laquelle vous travaillez, plutôt qu'une référence .

0

foreach fonctionne avec tout ce qui implémente l'interface IEnumerable. Afin d'éviter les problèmes de synchronisation, l'énumérable ne doit jamais être modifié lors de son itération. Les problèmes surviennent si vous ajoutez ou supprimez des éléments d'un autre thread pendant l'itération: en fonction de l'endroit où vous vous trouvez, vous risquez de manquer un élément ou d'appliquer votre code à un élément supplémentaire. Ceci est détecté par le runtime (dans certains cas ou tous ???) Et lève une exception:

System.InvalidOperationException was unhandled 
    Message="Collection was modified; enumeration operation may not execute." 

foreach cherche à obtenir point suivant chaque itération qui peut causer des problèmes si vous modifiez d'un autre fil en même temps.

1

La commande foreach utilise l'interface IEnumerable pour effectuer une boucle dans la collection. L'interface n'a défini que des méthodes pour parcourir une collection et obtenir l'élément en cours, il n'existe aucune méthode pour mettre à jour la collection. Comme l'interface ne définit que les méthodes minimales requises pour lire le collecton dans un sens, l'interface peut être mise en œuvre par un large éventail de collections.

Étant donné que vous n'accédez qu'à un seul élément à la fois, la totalité de la collection n'a pas besoin d'exister en même temps. Ceci est par exemple utilisé par les expressions LINQ, où il crée le résultat à la volée au fur et à mesure que vous le lisez, au lieu de créer d'abord le résultat entier, puis de le parcourir en boucle.

1

Je ne sais pas ce que vous voulez dire en lecture seule, mais je suppose que la compréhension de ce que la boucle foreach est sous le capot aidera. Il est du sucre syntaxique et pourrait aussi être écrit quelque chose comme ceci:

IEnumerator enumerator = list.GetEnumerator(); 
while(enumerator.MoveNext()) 
{ 
    T element = enumerator.Current; 
    //body goes here 
} 

Si vous changez la collection (liste), il devient difficile impossible de savoir comment traiter l'itération. Assigner à l'élément (dans la version de foreach) pourrait être vu comme essayant d'assigner à l'énumérateur. Courant qui est en lecture seule ou essayant de changer la valeur du local tenant une référence à l'énumérateur.Current auquel cas vous pourriez aussi bien présenter un local, car il n'a plus rien à voir avec la liste énumérée.

Questions connexes