Изменение переменной итерации foreach и различия в реализации между C # и C ++ / CLI - PullRequest
1 голос
/ 10 января 2010

Рассмотрим следующий код C #.

string[] stringArray = new string[10];
foreach (string s in stringArray)
    s = "a new string";  // Compiler error - Can't assign to foreach iteration variable

Теперь рассмотрим следующий действительный код C ++ / CLI.

array<String^>^ stringArray = gcnew array<String^>(10);
for each(String^% s in stringArray)
    s = "a new string"; 

Когда foreach используется с типом массива, компилятор переводит его в обычный цикл for. Эта реализация одинакова для C # и C ++ / CLI. Поэтому мне интересно, может ли C ++ / CLI разрешить это, почему не компилятор C #?

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

Есть мысли?

В качестве примечания ниже приведен также допустимый код C ++ / CLI, но он не даст ожидаемого результата.

List<String^>^ stringList = gcnew List<String^>(10);
for each(String^% s in stringList)
    s = "a new string"; // I think this should be prevented by compiler as it makes no sense.

Ответы [ 5 ]

6 голосов
/ 10 января 2010

Здесь, кажется, есть три разных вопроса:

  1. Почему C ++ позволяет вам присвоить for each переменную итерации?
  2. Почему нет C #?
  3. Почему компиляторы C ++ и C # ведут себя по-разному?

Ответы довольно просты:

  1. Поскольку команда C ++ не решила явно запретить это, и технически переменная итерации является просто локальной переменной - она ​​не получает специальной обработки.

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

  3. Потому что команда C ++ и команда C # - это разные команды. C ++ всегда был языком, который позволяет вам стрелять себе в ногу, если вы того пожелаете, и заходит так далеко, что вручает вам заряженный пистолет. C # часто пытается применить правила «правильного кода».

Здесь может быть еще один вопрос:

  • Зачем C # компилировать foreach в for, если не разрешено присваивание? Или наоборот - почему не разрешить это, если так оно и будет скомпилировано?

На самом деле есть два ответа на этот вопрос:

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

    Эта небольшая настройка производительности - просто деталь реализации ; он не является частью спецификации , и если бы вы смогли написать код, который зависит от конкретной реализации, команда C # не смогла бы изменить эту реализацию позже, не нарушив существующий код. Они, конечно, хотели бы избежать такой ситуации.

  • Потому что на самом деле не имеет значения, как можно подумать. Если бы вы могли выполнять присваивание в C #, вы бы не изменяли массив, а только содержимое локальной переменной, которая изначально содержала что-то из массива. Это опять-таки подпадает под категорию «затрудняет написание некорректного кода» - если конструкция did позволяет вам присвоить переменную, некоторые программисты могут подумать, что это фактически изменит коллекция , которая была бы ложной.

Я думаю, это должно объяснить это очень хорошо.

1 голос
/ 10 января 2010

Это потому, что C # использует дым и зеркала (то есть магию). Переменная, которую вы возвращаете в foreach, не является фактическим элементом в массиве, это копия, сделанная объектом итерации ... или что-то еще. Мы на самом деле не знаем (ну, мы понимаем, но нам нужно разбить уровень абстракции и посмотреть на реализацию объекта итератора.)

Если вы хотите изменить значения в массиве, вам придется иметь дело непосредственно с интерфейсом массива для доступа к этим элементам. В c ++ это происходит, но в основном по ошибке (многие из c ++ похожи на это, оригинальные реализации были на самом деле макросами и предварительной обработкой). В C # это явно определено, чтобы не работать - таким образом сообщение компилятора. (См. Раздел 5.3.3.16 в спецификации. )

0 голосов
/ 18 января 2010

Aaronaught, цикл не должен возвращать функцию, он просто должен создать строковую переменную "Token" и инициализировать ее значением из tokenize. После этого это не имеет никакого отношения к внутренним элементам токенизации. Я пытался понять, что переменная цикла должна быть не магическим указателем на коллекцию, а объектом, который создается с помощью «string Token =», как если бы он был создан в цикле for или while.

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

В C ++ это работает.

0 голосов
/ 18 января 2010

Ре Хоган: Да, копия сделана им. Фактически, когда я столкнулся с этим (сегодня), я полагал, что это изменяемая копия:

foreach (string Token in tokenize(Command))
{
foreach (KeyValuePair<string, string> Replacement in TokensToReplace)
    {
    if (Token==Replacement.Key)
        {
        Token = Replacement.Value;
        }
    }
TokenList.Add(Token);
}

Я думаю, к сожалению, это не работает.

0 голосов
/ 10 января 2010

Я думаю, что вы ответили на свой вопрос, заявив, что для случая C ++:

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

Если вы кодируете C # как цикл for, вы можете сделать это.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...