Почему я не могу изменить переменная цикла в элементе foreach?

Почему цикл foreach предназначен только для чтения? Какие есть причины для этого?

9
задан George 24 March 2011 в 21:38
поделиться

6 ответов

Я не совсем понимаю, что вы подразумеваете под «циклом только для чтения», но я предполагаю, что вы хотите знать, почему он не компилируется:

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

Приведенный выше код даст следующая ошибка компиляции:

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

Почему это запрещено? Попытка присвоить ему, вероятно, не принесет того, что вы хотите - это не изменит содержимое исходной коллекции. Это потому, что переменная x не является ссылкой на элементы в списке - это копия. Чтобы избежать написания кода с ошибками, компилятор запрещает это.

22
ответ дан 4 December 2019 в 07:04
поделиться

Я бы предположил, что дело в том, как итератор перемещается по списку.

Скажем, у вас есть отсортированный список:

Alaska
Nebraska
Ohio

В середине

foreach(var s in States)
{
}

Вы делаете States.Add("Missouri")

Как вы это обрабатываете? Переходите ли вы к Миссури, даже если вы уже прошли этот индекс.

5
ответ дан 4 December 2019 в 07:04
поделиться

Если под этим вы подразумеваете:

Почему бы мне не изменить коллекцию, над которой выполняется foreach?

Нет никакой уверенности в том, что элементы, которые вы получаете, выходят в определенном порядке, и что добавление или удаление элемента не приведет к изменению порядка элементов в коллекции или даже к тому, что Enumerator станет недействительным.

Представьте себе, что вы выполняете следующий код:

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

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

Как только вы перейдете в цикл, где i равно 6 (т.е. после удаления элемента), может произойти что угодно. Перечислитель мог стать недействительным из-за удаления элемента, все могло "перетасоваться на один" в базовой коллекции, в результате чего элемент занял место удаленного, то есть вы "пропустили" один элемент.

Если вы имели в виду "почему я не могу изменить значение, которое предоставляется на каждой итерации", то, если коллекция, с которой вы работаете, содержит типы значений, любые сделанные вами изменения не будут сохранены, поскольку вы работаете со значением, а не с ссылкой.

3
ответ дан 4 December 2019 в 07:04
поделиться

Не уверен, что вы имеете в виду под «только для чтения», но я предполагаю, что понимание того, что скрывается за циклом foreach, поможет. Это синтаксический сахар, и его также можно было бы написать примерно так:

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

Если вы измените коллекцию (список), становится трудно или невозможно понять, как обрабатывать итерацию. Присвоение элементу (в версии foreach) можно рассматривать либо как попытку присвоить enumerator.Current, который доступен только для чтения, либо как попытку изменить значение локального, содержащего ссылку на enumerator.Current, и в этом случае вы также можете ввести local, потому что он больше не имеет ничего общего с перечисленным списком.

1
ответ дан 4 December 2019 в 07:04
поделиться

foreach работает со всем, что реализует интерфейс IEnumerable. Чтобы избежать проблем с синхронизацией, перечислимое никогда не должно быть изменено во время итерации по нему.

Проблемы возникают, если вы добавляете или удаляете элементы в другом потоке во время итерации: в зависимости от того, где вы находитесь, вы можете пропустить элемент или применить свой код к лишнему элементу. Это обнаруживается средой выполнения (в некоторых случаях или во всех???) и вызывает исключение:

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

foreach пытается получить следующий элемент на каждой итерации, что может вызвать проблемы, если вы одновременно изменяете его из другого потока.

0
ответ дан 4 December 2019 в 07:04
поделиться

Команда foreach использует интерфейс IEnumerable для циклического просмотра коллекции. В интерфейсе определены только методы для пошагового перехода по коллекции и получения текущего элемента, методов для обновления коллекции нет.

Поскольку интерфейс определяет только минимальные методы, необходимые для чтения коллекции в одном направлении, интерфейс может быть реализован с помощью широкого диапазона коллекций.

Поскольку вы получаете доступ только к одному элементу за раз, вся коллекция не должна существовать одновременно. Это, например, используется выражениями LINQ, где он создает результат на лету, когда вы его читаете, вместо того, чтобы сначала создать весь результат, а затем позволить вам пройти его по циклу.

1
ответ дан 4 December 2019 в 07:04
поделиться
Другие вопросы по тегам:

Похожие вопросы: