Подтвердить что ты не робот

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

Как я понимаю, переменная итерации С# foreach неизменна.

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

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

     Plot(Location);
}

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

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

     Plot(Location);        
     i = Location;
}

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

Мне любопытно, что является основанием для того, чтобы сделать итератор неизменным?


Изменить:

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

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

// The game 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;
    }
}

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

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

4b9b3361

Ответ 1

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

Object o = 15;
o = "apples";

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

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 Law, если вы будете следовать Coding Horror. Переменная должна означать только одну вещь.

Ответ 2

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

string[] names = { "Jon", "Holly", "Tom", "Robin", "William" };

foreach (string name in names)
{
    name = name + " Skeet";
}

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

Ответ 3

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

using System;
using System.Collections.Generic;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {

            List<MyObject> colection =new List<MyObject>{ 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;
        }
    }
}

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

Ответ 4

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

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

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

Ответ 5

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

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

Ответ 6

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

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