2015-12-16 2 views
9

Используя Visual Studio 2013, я пытаюсь воспроизвести полученную информацию, упомянутую в блоге Эрика Липперта "Closing over the loop variable considered harmful".Воспроизведение «закрыть по переменной foreach» gotcha

В свойствах проекта я выбрал «C# 3.0» в качестве языковой версии (Build> Advanced ...). Кроме того, я выбрал «.NET Framework 3.5» как целевую структуру, как будто я считаю, что это не обязательно, поскольку это касается только языка.

Запуск его код:

using System; 
using System.Collections.Generic; 

namespace Project1 
{ 
    class Class1 
    { 
     public static void Main(string[] args) 
     { 
      var values = new List<int>() { 100, 110, 120 }; 
      var funcs = new List<Func<int>>(); 
      foreach (var v in values) 
      { 
       funcs.Add(() => v); 
      } 
      foreach (var f in funcs) 
       Console.WriteLine(f()); 
     } 
    } 
} 

Ожидаемый результат:

 
120 
120 
120 

Фактический выход:

 
100 
110 
120 

Как ответил Eric Lippert himself в "Is there a reason for C#'s reuse of the variable in a foreach?":

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

Что я делаю неправильно?

ответ

9

Ответ Скотта правильный, но он может использовать некоторые дополнительные разъяснения.

Проблема заключается в том, что переключатель «языковой версии» не делает то, что вы думаете. Это, на мой взгляд, немного неправильно, так как это довольно вводит в заблуждение. Переключатель «language version» не означает «использовать старый компилятор»; это не режим совместимости.

Скорее, это означает «использовать текущий компилятор и произвести ошибку, если я использую функцию, которая недоступна в выбранной версии языка».

Причина этого переключателя заключается в том, что один человек из команды разработчиков может «опробовать» новую версию компилятора, чтобы убедиться, что их код все еще работает, но знайте, прежде чем они проведут проверку того, что они случайно не использовались языковой функции, что компиляторы их товарищей по команде задохнутся. Поэтому, если вы установите версию языка на 3.0, тогда «динамический» не будет работать (поскольку он был добавлен в C# 4.0), но все равно какая-либо версия установленного компилятора.

Как указывает Скотт, если вы хотите использовать старый компилятор, вам придется на самом деле найти копию старого компилятора на своей машине и использовать его явно.

См. http://ericlippert.com/2013/04/04/what-does-the-langversion-switch-do/ еще несколько примеров того, что этот переключатель делает и не делает.

+0

Когда код действительно сломается? Я думаю, если бы код опирался на ошибку в старом компиляторе, который затем фиксируется или новый компилятор вводит ошибку. Существуют ли другие сценарии, где это может быть полезно? – xehpuk

+0

@xehpuk: Я не уверен, что понимаю ваш вопрос. Разумеется, код, основанный на ошибках фиксированного компилятора, будет прерываться независимо от значения переключателя langversion. Команда компилятора очень старательно сохраняет обратную совместимость при исправлении ошибок, но это не всегда возможно. –

+0

Вы правы, это все тот же компилятор. Таким образом, вариант использования в основном состоит в том, что один член проекта имеет более новую версию компилятора, но не может легко использовать старшую (потому что, например, он использует более новую версию VS, которая по какой-либо причине не имеет этого параметра). – xehpuk

8

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

Чтобы получить поведение, вы должны найти копию старого компилятора (VS 2012 или старше). Когда Эрик сказал, что «изменение не будет перенесено обратно на предыдущие версии C#», он имел в виду, что они не будут выпускать обновление для VS2012, чтобы изменить поведение в этом компиляторе (когда сообщение, которое вы цитировали, было написано VS2013, только что вышло и VS2012 все еще широко использовался).

EDIT: Если вы действительно хотите воссоздать поведение, вам нужно написать собственное руководство для каждого цикла.

public static void Main(string[] args) 
{ 
    var values = new List<int>() { 100, 110, 120 }; 
    var funcs = new List<Func<int>>(); 

    using (var enumerator = values.GetEnumerator()) 
    { 
     int v; 
     while (enumerator.MoveNext()) 
     { 
      //int v; // In C# 5+ the variable is declared here. 
      v = enumerator.Current; 
      funcs.Add(() => v); 
     } 
    } 

    foreach (var f in funcs) 
     Console.WriteLine(f()); 
} 

Это будет выводить ожидаемый результат.

+4

Вам не нужно было устанавливать старую версию VS для примера с игрушкой. Вы можете сделать это, выполнив csc вручную из C: \ Windows \ Microsoft.NET \ Framework \ v3.5 \ csc.exe –

Смежные вопросы