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



Answers

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

Для меня наиболее убедительным аргументом является то, что наличие новой переменной в каждой итерации будет противоречить циклу стиля 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 # .

Question

При использовании лямбда-выражений или анонимных методов в C # мы должны быть осторожны с доступом к модифицированной ловушке закрытия . Например:

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

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

Как объясняется here , это происходит потому, что переменная s объявленная в вышеперечисленном петле foreach , переводится так же, как в компиляторе:

string s;
while (enumerator.MoveNext())
{
   s = enumerator.Current;
   ...
}

вместо этого:

while (enumerator.MoveNext())
{
   string s;
   s = enumerator.Current;
   ...
}

Как указано here , нет никаких преимуществ в производительности для объявления переменной за пределами цикла, и при нормальных обстоятельствах единственной причиной, по которой я могу думать, для этого является то, что вы планируете использовать переменную за пределами цикла:

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 таким образом, что вы не могли бы, если бы они были скомпилированы с переменной с внутренней областью, или это просто произвольный выбор, который был сделан до того, как анонимные методы и лямбда-выражения были доступны или распространены, и которые с тех пор не был пересмотрен?




В 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 ).




Links