2010-08-23 3 views
23

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

string[] sArray = new string[5]; 

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

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

+8

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

+0

Дублирующий вопрос: http://stackoverflow.com/questions/776430/why-is-the-iteration-variable-in-ac-foreach-statement-read-only – chilltemp

ответ

56

Вот код:

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

Вот a rough approximation, что компилятор делает с этим:

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"; 
} 
+3

+1 для рассечения синтаксического сахара :) –

+2

Отличный ответ! Наконец, тот, который правильно объясняет, что происходит. –

+0

Я никогда не пойму их компиляторов:/ – BoltClock

2

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

+1

Я не уверен, что действительно объясняет, почему «текущий переменная элемента "доступна только для чтения. –

+1

@Kirk: поскольку текущий элемент не копируется при чтении объекта 'IEnumerable'. – BoltClock

+0

@Kirk Woll, вы не можете изменять значения с помощью 'IEnumerator'. Свойство 'IEnumerator.Current' - только для чтения, у него нет setter. –

4

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

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

+0

Ну, вы можете изменить его, если вы его не назначаете. Например, если вы «отдаете предпочтение» над кучей объектов, вы можете изменить внутренности объекта, вы просто не можете назначить сам объект. – Robaticus

5

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

Кроме того, из MSDN:

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

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

5

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

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

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.

+0

Это ключевое слово yield было первым, что пришло в голову, как реальная вещь, от которой мы защищены. «неожиданное поведение» может произойти, но C# редко защищает нас от неожиданного поведения, ваш пример, хотя стоит нас защитить. –

1

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

string[] sArray = new string[5]; 

for (int i=0;i<sArray.Length;i++) 
{ 
    item[i] = "Some Assignment.\r\n"; 
} 
+1

Вы можете уменьшить все это до одного оператора, используя 'Enumerable.Repeat'. –

2

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

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

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

+1

Строки не являются значениями. Что еще более важно, присвоение локальной переменной новому значению ** никогда не изменит значение ссылки в другом месте, если только это не параметр 'ref' (в этом случае это фактически та же ссылка). Может быть, вы должны сказать: «Потому что« item »имеет локальный охват ...»? –

+0

@ Даан Тао: Я удалил эту часть своего ответа; другие ответы здесь лучше объяснили, что я имел в виду. –

+0

Я подумал, что ты только что ошибся. Все всегда в безумной спешке, чтобы получить ответ сначала - я знаю, что я:) –

0

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

0

Вы не можете изменить список, который зацикливается через «ForEach».

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

+0

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

0

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

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