Почему мы не можем назначить переменную foreach итерации, тогда как мы можем полностью изменить ее с помощью метода доступа? - PullRequest
43 голосов
/ 20 октября 2011

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

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

Но следующее скомпилируется и запустится:

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

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

Ответы [ 9 ]

33 голосов
/ 20 октября 2011

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

32 голосов
/ 20 октября 2011

Второй не делает то же самое вообще. Он не меняет значение переменной item - он меняет свойство объекта, к которому относится это значение. Эти два значения only будут эквивалентны, если item является изменяемым типом значения - в этом случае вы должны изменить его в любом случае, поскольку изменяемые типы значений являются злыми. (Они ведут себя во всех отношениях, чего может ожидать неосторожный разработчик.)

Это так же, как это:

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

См. Мою статью о ссылках и значениях для получения дополнительной информации.

18 голосов
/ 20 октября 2011

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

Используйте цикл for, если вам нужно добавить / удалить / изменить элементы в коллекции:

for (int i = 0; i < MyObjectList.Count; i++)
{
    MyObjectList[i] = new MyObject();
}
11 голосов
/ 20 октября 2011

Если вы посмотрите на спецификацию языка, вы увидите, почему это не работает:

В спецификациях говорится, что foreach расширяется до следующего кода:

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

Как видите, текущий элемент используется для вызова MoveNext (). Таким образом, если вы измените текущий элемент, код «потерян» и не сможет перебрать коллекцию. Поэтому изменение элемента на что-то другое не имеет смысла, если вы видите, какой код на самом деле создает компилятор.

4 голосов
/ 20 октября 2011

Это можно сделать изменяемым item. Мы могли бы изменить способ создания кода так, чтобы:

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

Стало эквивалентно:

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

И тогда он скомпилируется. Однако это не повлияет на коллекцию.

И есть руб. Код:

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

У человека есть два разумных способа думать об этом. Во-первых, item является просто заполнителем, и его изменение ничем не отличается от изменения item в:

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

Другое дело, что изменение предмета фактически изменит коллекцию.

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

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

2 голосов
/ 20 октября 2011

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

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

    ....
}

Что касается второго, то здесь есть допустимые варианты использования - вы можете захотеть перебрать перечисление автомобилей и вызвать .Drive () на каждом или установите car.gasTank = full;

1 голос
/ 18 марта 2017

Используйте цикл for вместо цикла foreach и присвойте значение. это будет работать

foreach (var a in FixValue)
{
    for (int i = 0; i < str.Count(); i++)
    {
       if (a.Value.Contains(str[i]))
           str[i] = a.Key;
    }
 }
1 голос
/ 20 октября 2011

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

Но если у вас есть элемент, он похож на любой другой объект, и вы можете изменять его любым (разрешенным) способом.

1 голос
/ 20 октября 2011

Суть в том, что вы не можете изменить саму коллекцию , перебирая ее.Абсолютно законно и обычно модифицировать объекты, которые дает итератор.

...