Почему мы не можем назначить переменную итерации foreach, тогда как мы можем полностью ее модифицировать с помощью аксессора? - программирование

Почему мы не можем назначить переменную итерации foreach, тогда как мы можем полностью ее модифицировать с помощью аксессора?

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

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

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

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

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

4b9b3361

Ответ 1

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

Ответ 2

Второй не делает то же самое. Это не меняет значение переменной item - это изменение свойства объекта, к которому относится это значение. Эти два будут эквивалентны только в том случае, если 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");

Дополнительную информацию см. в статье о ссылках и значениях.

Ответ 3

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

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

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

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

Ответ 4

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

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

for (int i = 0; i < MyObjectList.Count; i++)
{
    MyObjectList[i] = new MyObject();
}

Ответ 5

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

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

    ....
}

Как и во втором случае, существуют допустимые варианты использования: вы можете перебрать нумерацию машин и вызвать .Drive() на каждом из них или установить car.gasTank = full;

Ответ 6

В нем можно было бы сделать 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

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

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

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

Ответ 7

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

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

Ответ 8

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

foreach (var a in FixValue)
{
    for (int i = 0; i < str.Count(); i++)
    {
       if (a.Value.Contains(str[i]))
           str[i] = a.Key;
    }
 }

Ответ 9

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