Почему операторы присваивания (=) недопустимы в цикле foreach? - PullRequest
26 голосов
/ 23 августа 2010

Почему операторы присваивания (=) недопустимы в цикле foreach? Я использую C #, но я предполагаю, что аргумент такой же, как и для других языков, которые поддерживают foreach (например, PHP). Например, если я сделаю что-то вроде этого:

string[] sArray = new string[5];

foreach (string item in sArray)
{
   item = "Some assignment.\r\n";
}

Я получаю сообщение об ошибке: «Невозможно назначить элементу, поскольку он является итерационной переменной foreach».

Ответы [ 10 ]

61 голосов
/ 23 августа 2010

Вот ваш код:

foreach (string item in sArray)
{
   item = "Some assignment.\r\n";
}

Вот грубое приближение того, что компилятор делает с этим:

using (var enumerator = sArray.GetEnumerator())
{
    string item;
    while (enumerator.MoveNext())
    {
        item = enumerator.Current;

        // Your code gets put here
    }
}

Считывается свойство IEnumerator<T>.Current-только, но на самом деле это не имеет значения, поскольку вы пытаетесь присвоить локальной переменной item новое значение.Проверка во время компиляции, которая не позволяет вам сделать это, в основном защищает вас от выполнения чего-то, что не будет работать так, как вы ожидаете (т.е. изменение локальной переменной и отсутствие влияния на базовую коллекцию / последовательность).

Если вы хотите изменить внутренние элементы индексированной коллекции, такие как string[], при перечислении, традиционный способ - использовать цикл for вместо foreach:

for (int i = 0; i < sArray.Length; ++i)
{
    sArray[i] = "Some assignment.\r\n";
}
5 голосов
/ 23 августа 2010

Потому что так сказано в спецификации языка.

А если серьезно, не все последовательности являются массивами или вещами, которые могут быть логически изменены или записаны. Например:

foreach (var i in Enumerable.Range(1, 100)) {
   // modification of `i` will not make much sense here.
}

Хотя технически можно было бы i = something; изменить локальную переменную, это может вводить в заблуждение (вы можете подумать, что это действительно что-то меняет под капотом, и это не так).

Для поддержки такого рода последовательностей, IEnumerable<T> не требует доступа set для его свойства Current, что делает его доступным только для чтения. Таким образом, foreach не может изменить базовую коллекцию (если она существует), используя свойство Current.

5 голосов
/ 23 августа 2010

Цикл foreach предназначен для итерации по объектам в коллекции, а не для назначения объектов - это просто дизайн языка.

Также от MSDN:

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

Ключевое слово foreach просто перечисляет IEnumerable экземпляров (получение Экземпляры IEnumerator, вызывая GetEnumerator () метод). IEnumerator только для чтения, поэтому значения не могут изменить с помощью IEnumerator = нельзя изменено с использованием контекста foreach.

4 голосов
/ 23 августа 2010

Поскольку вы не можете использовать цикл foreach для изменения массива, через который вы проходите. Цикл повторяет по массиву, поэтому, если вы попытаетесь изменить то, через что он повторяется, может произойти непредвиденное поведение. Кроме того, как указали Дарин и Д.М., вы перебираете IEnumerable, который сам по себе доступен только для чтения.

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

2 голосов
/ 23 августа 2010

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

string[] sArray = Enumerable.Repeat("Some assignment.\r\n", 5).ToArray();

Конструкции более высокого уровня почти всегда можно использовать вместо этого вида цикла в C #. (И C ++, но это совсем другая тема)

2 голосов
/ 23 августа 2010

Поскольку IEnumerable только для чтения.

1 голос
/ 23 августа 2010

Вы не можете изменить массив, через который вы проходите. Вместо этого используйте следующий код:

string[] sArray = new string[5]; 

for (int i=0;i<sArray.Length;i++)
{
    item[i] = "Some Assignment.\r\n";
}
0 голосов
/ 23 августа 2010

Было бы вполне возможно позволить ему измениться. Однако что это значит? Он будет выглядеть так, как если бы базовое перечисление было изменено, но это не так (можно было бы это допустить, но у этого есть свои недостатки).

Таким образом, у вас будет код, который люди, естественно, прочитают как указывающий на нечто иное, чем то, что на самом деле произошло. Учитывая, что цель компьютерного языка в первую очередь должна быть понятна людям (компиляторы имеют дело с балансом, установленным против них, если вы не используете ассемблер, машинный код или правильно названный Brainf ** k), это будет указывать на недостаток в язык.

0 голосов
/ 23 августа 2010

Вы не можете изменить список, который проходит через «ForEach».

Лучший вариант - просто создать временный список для хранения элементов, которые вы хотите использовать.

0 голосов
/ 23 августа 2010

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

...