При использовании лямбда-выражений или анонимных методов в C # мы должны опасаться доступа к измененной ловушке замыкания . Например:
foreach (var s in strings)
{
query = query.Where(i => i.Prop == s); // access to modified closure
...
}
Из-за измененного закрытия приведенный выше код приведет к тому, что все Where
предложения в запросе будут основаны на конечном значении s
.
Как объясняется здесь , это происходит потому, что s
переменная, объявленная в foreach
цикле выше, переводится следующим образом в компилятор:
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
циклами таким образом, что вы не смогли бы, если бы они были скомпилированы с переменной внутренней области видимости, или это просто произвольный выбор, который был сделан до того, как анонимные методы и лямбда-выражения были доступны или распространены, и который не было пересмотрено с тех пор?
String s; foreach (s in strings) { ... }
?foreach
лямда-выражениях, которые приводят к тому же коду, как показано ОП ...Ответы:
Ваша критика полностью оправдана.
Я обсуждаю эту проблему подробно здесь:
Закрытие по переменной цикла считается вредным
Последний. Спецификация C # 1.0 фактически не говорила, была ли переменная цикла внутри или снаружи тела цикла, так как она не имела заметного различия. Когда семантика замыкания была введена в C # 2.0, был сделан выбор поместить переменную цикла вне цикла, в соответствии с циклом «for».
Я думаю, что будет справедливо сказать, что все сожалеют об этом решении. Это одна из худших ошибок в C #, и мы собираемся предпринять решительные изменения, чтобы исправить это. В C # 5 переменная цикла foreach будет логически находиться внутри тела цикла, и поэтому замыкания будут каждый раз получать новую копию.
for
Цикл не будет изменен, и изменение не будет «обратно портированы» в предыдущие версии C #. Поэтому вы должны продолжать соблюдать осторожность при использовании этой идиомы.источник
foreach
это «безопасно», ноfor
это не так.То, о чем вы спрашиваете, подробно освещено Эриком Липпертом в его блоге. Закрытие по переменной цикла, считающейся вредной, и ее продолжение.
Для меня наиболее убедительным аргументом является то, что наличие новой переменной в каждой итерации несовместимо с
for(;;)
циклом стиля. Ожидаете ли вы иметь новоеint i
в каждой итерацииfor (int i = 0; i < 10; i++)
?Наиболее распространенная проблема с этим поведением заключается в закрытии переменной итерации, и она имеет простой обходной путь:
Мой пост в блоге об этой проблеме: закрытие над переменной foreach в C # .
источник
ref
.cut
для refs / vars иcute
для хранения оцененных значений в частично оцененных замыканиях).Будучи укушенным этим, я имею привычку включать локально определенные переменные во внутреннюю область видимости, которую я использую для переноса в любое замыкание. В вашем примере:
Я делаю:
Если у вас есть эта привычка, вы можете избежать ее в очень редком случае, который вы действительно намеревались привязать к внешним областям. Если честно, я не думаю, что когда-либо делал это.
источник
В C # 5.0 эта проблема исправлена, и вы можете закрыть переменные цикла и получить ожидаемые результаты.
Спецификация языка говорит:
источник