Ao usar expressões lambda ou métodos anônimos em C #, precisamos ter cuidado com o acesso à armadilha de fechamento modificada . Por exemplo:
foreach (var s in strings)
{
query = query.Where(i => i.Prop == s); // access to modified closure
...
}
Devido ao fechamento modificado, o código acima fará com que todas as Where
cláusulas da consulta sejam baseadas no valor final des
.
Como explicado aqui , isso acontece porque a s
variável declarada no foreach
loop acima é traduzida assim no compilador:
string s;
while (enumerator.MoveNext())
{
s = enumerator.Current;
...
}
em vez de assim:
while (enumerator.MoveNext())
{
string s;
s = enumerator.Current;
...
}
Conforme indicado aqui , não há vantagens de desempenho em declarar uma variável fora do loop e, em circunstâncias normais, a única razão pela qual posso pensar nisso é se você planeja usar a variável fora do escopo do loop:
string s;
while (enumerator.MoveNext())
{
s = enumerator.Current;
...
}
var finalString = s;
No entanto, variáveis definidas em um foreach
loop não podem ser usadas fora do loop:
foreach(string s in strings)
{
}
var finalString = s; // won't work: you're outside the scope.
Portanto, o compilador declara a variável de uma maneira que a torna altamente propensa a um erro que geralmente é difícil de encontrar e depurar, sem produzir benefícios perceptíveis.
Existe algo que você pode fazer com foreach
loops dessa maneira que não seria possível se eles fossem compilados com uma variável com escopo interno ou essa é apenas uma escolha arbitrária que foi feita antes que métodos anônimos e expressões lambda estivessem disponíveis ou comuns e que não foi revisado desde então?
String s; foreach (s in strings) { ... }
?foreach
, mas sobre expressões Lamda, resultando em código semelhante como mostrado pelo OP ...Respostas:
Sua crítica é inteiramente justificada.
Discuto esse problema em detalhes aqui:
Fechar sobre a variável do loop considerada prejudicial
O último. A especificação C # 1.0, na verdade, não dizia se a variável do loop estava dentro ou fora do corpo do loop, pois não fazia diferença observável. Quando a semântica de fechamento foi introduzida no C # 2.0, optou-se por colocar a variável do loop fora do loop, consistente com o loop "for".
Eu acho que é justo dizer que todos lamentam essa decisão. Essa é uma das piores "pegadinhas" do C #, e vamos fazer a alteração final para corrigi-la. No C # 5, a variável de loop foreach estará logicamente dentro do corpo do loop e, portanto, os fechamentos receberão uma cópia nova sempre.
O
for
loop não será alterado e a alteração não será "portada para trás" para versões anteriores do C #. Portanto, você deve continuar tomando cuidado ao usar esse idioma.fonte
foreach
é "seguro", masfor
não é.O que você está perguntando é totalmente abordado por Eric Lippert em seu post no blog, Closing over the loop, a variável considerada prejudicial e sua sequência.
Para mim, o argumento mais convincente é que ter nova variável em cada iteração seria inconsistente com o
for(;;)
loop de estilo. Você esperaria ter um novoint i
em cada iteração defor (int i = 0; i < 10; i++)
?O problema mais comum com esse comportamento é fechar a variável de iteração e possui uma solução fácil:
Minha postagem no blog sobre esse problema: Encerramento sobre a variável foreach em C # .
fonte
ref
.cut
para refs / vars ecute
para manter valores avaliados em fechamentos parcialmente avaliados).Tendo sido mordido por isso, tenho o hábito de incluir variáveis definidas localmente no escopo mais interno que uso para transferir para qualquer fechamento. No seu exemplo:
Eu faço:
Depois de ter esse hábito, você pode evitá-lo no caso muito raro de realmente pretender vincular-se aos escopos externos. Para ser sincero, acho que nunca fiz isso.
fonte
No C # 5.0, esse problema foi corrigido e você pode fechar as variáveis de loop e obter os resultados esperados.
A especificação da linguagem diz:
fonte