Есть ли причина повторного использования C # переменной в foreach?
При использовании lambda-выражений или анонимных методов в C # мы должны быть осторожны с доступом к модифицированной ловушке закрытия . Например:
foreach (var s in strings) { query = query.Where(i => i.Prop == s); // access to modified closure ... }
Из-за модифицированного закрытия вышеупомянутый код приведет к тому, что все предложения Where
в запросе будут основаны на конечном значении s
.
Как объясняется здесь , это происходит потому, что переменная s
объявленная в вышеперечисленном петле foreach
, переводится так же, как в компиляторе:
- Angle $ scope. $ Apply vs $ timeout как безопасный $ apply
- В чем разница между локальной переменной, полем экземпляра, входным параметром и полем classа?
- jquery - область внутри $ (document) .ready ()?
- Почему многонаследованные функции с таким же именем, но с разными сигнатурами не рассматриваются как перегруженные функции?
- Есть ли разница в производительности между «let» и «var» в JavaScript
string s; while (enumerator.MoveNext()) { s = enumerator.Current; ... }
вместо этого:
while (enumerator.MoveNext()) { string s; s = enumerator.Current; ... }
Как указано здесь , нет никаких преимуществ в производительности для объявления переменной за пределами цикла, и при нормальных обстоятельствах единственной причиной, по которой я могу думать, для этого является то, что вы планируете использовать переменную за пределами цикла:
string s; while (enumerator.MoveNext()) { s = enumerator.Current; ... } var finalString = s;
Однако переменные, определенные в цикле foreach
не могут использоваться вне цикла:
foreach(string s in strings) { } var finalString = s; // won't work: you're outside the scope.
Таким образом, компилятор объявляет переменную таким образом, что она сильно подвержена ошибкам, которые часто трудно найти и отладить, при этом не получая никаких ощутимых преимуществ.
Есть ли что-то, что вы можете сделать с циклами foreach
таким образом, что вы не могли бы, если бы они были скомпилированы с переменной с внутренней областью, или это просто произвольный выбор, который был сделан до того, как анонимные методы и lambda-выражения были доступны или распространены, и которые с тех пор не был пересмотрен?
- Управление значением «this» в событии jQuery
- Статические переменные в C ++
- Комбинированная область перекрывающихся кругов
- Лучше ли объявлять переменную внутри или снаружи цикла?
- Область видимости переменной C #: «x» не может быть объявлена в этой области, потому что она придавала бы значение «x»,
- Смешивание области видимости в C #
- Как найти файл моего царства?
- Сервлет перенаправляет на ту же страницу сообщение об ошибке
Компилятор объявляет переменную таким образом, что делает ее очень подверженной ошибкам, которые часто трудно найти и отладить, при этом не получая никаких ощутимых преимуществ.
Ваша критика полностью оправдана.
Здесь я подробно обсуждаю эту проблему:
Закрытие по переменной цикла считается вредным
Есть ли что-то, что вы можете делать с циклами foreach таким образом, что вы не могли бы, если бы они были скомпилированы с переменной внутреннего охвата? или это просто произвольный выбор, который был сделан до того, как анонимные методы и lambda-выражения были доступны или распространены и которые не были пересмотрены с тех пор?
Последний. Спецификация C # 1.0 фактически не указала, была ли переменная цикла внутри или снаружи тела цикла, поскольку она не делала заметной разницы. Когда семантика закрытия была введена в C # 2.0, был сделан выбор, чтобы поместить переменную цикла вне цикла, в соответствии с циклом «для».
Я считаю справедливым сказать, что все сожалеют об этом решении. Это один из худших «gotchas» на C #, и мы будем исправлять его, чтобы исправить это. В C # 5 переменная цикла foreach будет логически находиться внутри тела цикла, и поэтому замыкания будут получать новую копию каждый раз.
Цикл for
не будет изменен, и изменение не будет «обратно портировано» на предыдущие версии C #. Поэтому вы должны быть осторожны при использовании этой идиомы.
То, что вы просите, полностью покрывается Эриком Липпертом в его блоге. Закрытие по переменной цикла считается вредной и ее продолжением.
Для меня наиболее убедительным аргументом является то, что наличие новой переменной в каждой итерации будет противоречить циклу стиля 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 # .
Будучи укушенным этим, у меня есть привычка включать локально определенные переменные в самый внутренний объем, который я использую для передачи на любое закрытие. В вашем примере:
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.
Как только у вас есть эта привычка, вы можете избежать этого в очень редком случае, когда вы намеревались привязываться к внешним областям. Честно говоря, я не думаю, что когда-либо делал это.
В 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 ).
По-моему, это странный вопрос. Хорошо знать, как работает компилятор, но это только «полезно знать».
Если вы пишете код, который зависит от алгоритма компилятора, это плохая практика. И лучше переписать код, чтобы исключить эту зависимость.
Это хороший вопрос для собеседования. Но в реальной жизни я не сталкивался с какими-либо проблемами, которые я решил на собеседовании.
90% foreach использует для обработки каждого элемента коллекции (не для выбора или вычисления некоторых значений). иногда вам нужно вычислить некоторые значения внутри цикла, но не рекомендуется создавать цикл BIG.
Для вычисления значений лучше использовать выражения LINQ. Потому что, когда вы вычисляете много вещей внутри цикла, через 2-3 месяца, когда вы (или кто-либо еще) прочитаете этот код, человек не поймет, что это такое и как он должен работать.