Почему переменная Iteration в инструкции C # foreach доступна только для чтения?

Насколько я понимаю, переменная итерации foreach C # неизменна.

Это означает, что я не могу изменить iterator следующим образом:

foreach (Position Location in Map) { //We want to fudge the position to hide the exact coordinates Location = Location + Random(); //Compiler Error Plot(Location); } 

Я не могу напрямую изменять переменную iteratorа, и вместо этого я должен использовать цикл for

 for (int i = 0; i < Map.Count; i++) { Position Location = Map[i]; Location = Location + Random(); Plot(Location); i = Location; } 

Исходя из фона C ++, я вижу foreach в качестве альтернативы циклу for. Но с указанным выше ограничением я обычно отказываюсь от использования цикла for.

Мне любопытно, в чем же причина, почему iterator неизменен?


Редактировать:

Этот вопрос является скорее вопросом любопытства, а не вопросом кодирования. Я оценил ответы на кодирование, но не могу отметить их как ответы.

Кроме того, приведенный выше пример был слишком упрощен. Вот пример C ++, который я хочу сделать:

 // The game's rules: // - The "Laser Of Death (tm)" moves around the game board from the // start area (index 0) until the end area (index BoardSize) // - If the Laser hits a teleporter, destroy that teleporter on the // board and move the Laser to the square where the teleporter // points to // - If the Laser hits a player, deal 15 damage and stop the laser. for (int i = 0; i < BoardSize; i++) { if (GetItem(Board[i]) == Teleporter) { TeleportSquare = GetTeleportSquare(Board[i]); SetItem(Board[i], FreeSpace); i = TeleportSquare; } if (GetItem(Board[i]) == Player) { Player.Life -= 15; break; } } 

Я не могу сделать выше в C # foreach, потому что iterator i является неизменным. Я думаю (исправьте меня, если я ошибаюсь), это характерно для дизайна foreach на языках.

Меня интересует, почему iterator foreach неизменен.

Давайте начнем с глупых, но наглядных примеров:

 Object o = 15; o = "apples"; 

Ни в коем случае не создается впечатление, что мы просто превратили номер 15 в цепочку яблок. Мы знаем, что o – просто указатель. Теперь давайте сделаем это в форме iteratorа.

 int[] nums = { 15, 16, 17 }; foreach (Object o in nums) { o = "apples"; } 

Опять же, это ничего не делает. Или, по крайней мере, он ничего не смог бы скомпилировать. Это, конечно, не введет нашу строку в массив int – это недопустимо, и мы знаем, что o – это просто указатель.

Возьмем ваш пример:

 foreach (Position Location in Map) { //We want to fudge the position to hide the exact coordinates Location = Location + Random(); //Compiler Error Plot(Location); } 

Если бы это было скомпилировано, Location в вашем примере отобразится со ссылкой на значение в Map , но затем вы измените его на ссылку на новую Position (неявно созданную оператором добавления). Функционально это эквивалентно этому (что компилируется):

 foreach (Position Location in Map) { //We want to fudge the position to hide the exact coordinates Position Location2 = Location + Random(); //No more Error Plot(Location2); } 

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

Но что еще более важно, у вас нет причин для его назначения. Он представляет собой текущий элемент последовательности циклов. Присвоение ценности для него нарушает «принцип единой ответственности» или закон Curly, если вы будете следовать Coding Horror. Переменная должна означать только одну вещь.

Если переменная была изменчивой, это может привести к некорректному показу. Например:

 string[] names = { "Jon", "Holly", "Tom", "Robin", "William" }; foreach (string name in names) { name = name + " Skeet"; } 

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

Я думаю, что это искусственное ограничение, не нужно это делать. Чтобы доказать эту точку, используйте этот код в качестве соображения и возможное решение вашей проблемы. Проблема заключается в asign, но внутренние изменения объектов не представляют проблемы:

 using System; using System.Collections.Generic; namespace ConsoleApplication1 { class Program { static void Main(string[] args) { List colection =new List{ new MyObject{ id=1, Name="obj1" }, new MyObject{ id=2, Name="obj2"} }; foreach (MyObject b in colection) { // b += 3; //Doesn't work b.Add(3); //Works } } class MyObject { public static MyObject operator +(MyObject b1, int b2) { return new MyObject { id = b1.id + b2, Name = b1.Name }; } public void Add(int b2) { this.id += b2; } public string Name; public int id; } } } 

Я не знал об этих явлениях, потому что я всегда менял объекты так, как я описывал.

Оператор foreach работает с Enumerable. Вещь, которая отличается от Enumerable, заключается в том, что она не знает о коллекции в целом, она почти слепа.

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

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

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

См. Также этот вопрос: что является лучшим способом изменить список в foreach

Условием работы с объектами IEnumerable является то, что базовая коллекция не должна меняться, когда вы обращаетесь к ней с помощью Enumerable. Вы можете предположить, что объект Enumerable является снимком оригинальной коллекции. Поэтому, если вы попытаетесь изменить коллекцию при перечислении, это вызовет исключение. Однако извлеченные объекты в Enumeration не являются неизменяемыми вообще.

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

  • Почему частные поля являются приватными для типа, а не экземпляра?
  • Почему нельзя синхронизировать конструкторы Java?
  • C ++: обоснование правила скрытия
  • Почему C ++ нужен отдельный заголовочный файл?
  • Почему в C # не разрешены параметры const?
  • Ограничение дженериков с помощью ключевого слова 'super'
  • Почему шаблон функции не может быть частично специализированным?
  • Interesting Posts

    Может ли размер указателей варьироваться между данными и указателями функций?

    Может ли каталог быть добавлен к пути classа во время выполнения?

    Установка Windows 7 зависает после запуска «Запуск Windows …»

    Интернет-зависимый, необходимо программное обеспечение для контроля моего использования и блокировки доступа

    Ошибка 0xc0000260 при установке Windows 8 Consumer Preview на старом ноутбуке

    “Класс не зарегистрирован (Исключение из HRESULT: 0x80040154 (REGDB_E_CLASSNOTREG))”

    DropDownListДля выбора значения

    Фрагментация условного форматирования Excel

    Альтернативный DNS-сервер, который не разрешает после первого, не разрешает

    Ошибка: в этом контексте поддерживаются только примитивные типы или типы перечислений EF

    Могу ли я навсегда запретить обновлениям безопасности Java от установки панели инструментов Yahoo?

    Как получить информацию о принтере в .NET?

    favicon.png vs favicon.ico – почему я должен использовать PNG вместо ICO?

    LINQ Single vs First

    Вычисление и сохранение пространства в PostgreSQL

    Давайте будем гением компьютера.