Есть ли причина для повторного использования C # переменной в foreach?

Вы можете либо

  • Добавить метод в A или B, который вы вызываете.
     // to A
     public String AtoString() {
         return toString();
     }
     // OR to B
     public String AtoString() {
         return super.toString();
     }
    
  • Вставьте код A.toString () туда, где он «называется»
     // inlined A.toString()
     String ret = "I am A";
     System.out.println( ret );
    

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

1603
задан Community 23 May 2017 в 12:26
поделиться

5 ответов

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

Ваша критика полностью оправдана.

Я подробно обсуждаю эту проблему здесь:

Закрытие по переменной цикла считается вредным

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

Последний. Спецификация C # 1.0 фактически не говорила, была ли переменная цикла внутри или снаружи тела цикла, так как она не имела заметного различия. Когда семантика замыкания была введена в C # 2.0, был сделан выбор поместить переменную цикла вне цикла, в соответствии с циклом «for».

1111 Я думаю, что будет справедливо сказать, что все сожалеют об этом решении. Это одна из худших «ошибок» в C #, и мы собираемся предпринять решающее изменение, чтобы исправить это. В C # 5 переменная цикла foreach будет логически находиться внутри тела цикла, и поэтому замыкания будут получать свежую копию каждый раз.

Цикл for не будет изменен, и изменение не будет «перенесено обратно» в предыдущие версии C #. Поэтому вы должны продолжать соблюдать осторожность при использовании этой идиомы.

1361
ответ дан Eric Lippert 23 May 2017 в 12:26
поделиться

То, о чем вы спрашиваете, подробно освещено Эриком Липпертом в его посте в блоге Закрытие переменной цикла, считающейся вредной , и ее продолжение.

Для меня наиболее убедительным аргументом является то, что наличие новой переменной в каждой итерации было бы несовместимо с циклом стиля for(;;). Ожидаете ли вы иметь новый int i в каждой итерации for (int i = 0; i < 10; i++)?

Наиболее распространенная проблема с этим поведением - сделать замыкание по переменной итерации, и он имеет простой обходной путь:

foreach (var s in strings)
{
    var s_for_closure = s;
    query = query.Where(i => i.Prop == s_for_closure); // access to modified closure

Сообщение в моем блоге об этой проблеме: Закрытие по переменной foreach в C # .

182
ответ дан Krizz 23 May 2017 в 12:26
поделиться

Будучи укушенным этим, я имею привычку включать локально определенные переменные во внутреннюю область, которую я использую для переноса в любое замыкание. В вашем примере:

foreach (var s in strings)
{
    query = query.Where(i => i.Prop == s); // access to modified closure

я делаю:

foreach (var s in strings)
{
    string search = s;
    query = query.Where(i => i.Prop == search); // New definition ensures unique per iteration.

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

100
ответ дан Appulus 23 May 2017 в 12:26
поделиться

В C # 5.0 эта проблема исправлена, и вы можете закрыть циклические переменные и получить ожидаемые результаты.

Спецификация языка гласит:

8.8.4 Оператор foreach

(...)

Оператор foreach в форме

foreach (V v in x) embedded-statement

затем расширяется до:

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

(...)

Размещение v внутри цикла while важно для того, как оно захватывается любым анонимным функция, встречающаяся во встроенном операторе. Например:

int[] values = { 7, 9, 13 };
Action f = null;
foreach (var value in values)
{
    if (f == null) f = () => Console.WriteLine("First value: " + value);
}
f();

Если бы v было объявлено вне цикла while, оно было бы общим для всех итераций, и его значение после цикла for было бы конечным значением, 13, которое это то, что напечатал бы вызов f. Вместо этого, поскольку каждая итерация имеет свою собственную переменную v, переменная, захваченная f на первой итерации, будет продолжать содержать значение 7, которое и будет напечатано. ( Примечание: более ранние версии C # объявлены v вне цикла while. )

58
ответ дан Paolo Moretti 23 May 2017 в 12:26
поделиться

На мой взгляд, это странный вопрос. Хорошо знать, как работает компилятор, но это только «полезно знать».

Если вы пишете код, который зависит от алгоритма компилятора, это плохая практика. И лучше переписать код, чтобы исключить эту зависимость.

Это хороший вопрос для собеседования. Но в реальной жизни я не сталкивался с какими-либо проблемами, которые решал на собеседовании.

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

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

-1
ответ дан TemaTre 23 May 2017 в 12:26
поделиться
Другие вопросы по тегам:

Похожие вопросы: