Existe uma razão para reutilização de C # da variável em um foreach?

Ao usar expressões lambda ou methods anônimos em C #, temos que ter cuidado com o access a armadilhas de fechamento modificadas . 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 cláusulas Where da consulta sejam baseadas no valor final de s .

Como explicado aqui , isso acontece porque a variável s declarada no loop foreach 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; ... } 

Como apontado aqui , não há vantagens de desempenho para declarar uma variável fora do loop e, em circunstâncias normais, a única razão que posso pensar em fazer isso é 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, as variables ​​definidas em um loop foreach não podem ser usadas fora do loop:

 foreach(string s in strings) { } var finalString = s; // won't work: you're outside the scope. 

Assim, o compilador declara a variável de uma maneira que a torna altamente propensa a um erro que muitas vezes é difícil de encontrar e depurar, ao mesmo tempo em que não produz benefícios perceptíveis.

Existe algo que você pode fazer com loops foreach desta forma que você não poderia se eles fossem compilados com uma variável com escopo interno, ou isso é apenas uma escolha arbitrária que foi feita antes de methods anônimos e expressões lambda estarem disponíveis ou comuns, e quais não foi revisado desde então?

O compilador declara a variável de uma maneira que a torna altamente propensa a um erro que muitas vezes é difícil de encontrar e depurar, enquanto produz benefícios não perceptíveis.

Sua crítica é inteiramente justificada.

Eu discuto este problema em detalhes aqui:

Fechando a variável loop considerada nociva

Existe algo que você pode fazer com loops foreach desta forma que você não poderia se eles fossem compilados com uma variável com escopo interno? ou isso é apenas uma escolha arbitrária feita antes que methods anônimos e expressões lambda estivessem disponíveis ou fossem comuns, e que não foram revisados ​​desde então?

O último. A especificação C # 1.0, na verdade, não informou se a variável de loop estava dentro ou fora do corpo do loop, já que não fazia diferença observável. Quando a semântica de fechamento foi introduzida no C # 2.0, a opção foi feita para colocar a variável de loop fora do loop, consistente com o loop “for”.

Eu acho que é justo dizer que todos lamentam essa decisão. Este é um dos piores “pegadinhas” em C #, e vamos fazer a mudança para consertá-lo. No C # 5, a variável de loop foreach estará logicamente dentro do corpo do loop e, portanto, os closures receberão uma nova cópia toda vez.

O loop for não será alterado e a alteração não será “back ported” para versões anteriores do C #. Portanto, você deve continuar sendo cuidadoso ao usar esse idioma.

O que você está pedindo é totalmente coberto por Eric Lippert em seu post no blog Fechando a variável de loop considerada prejudicial e sua sequela.

Para mim, o argumento mais convincente é que ter uma nova variável em cada iteração seria inconsistente com o loop de estilo for(;;) . Você esperaria ter um novo int i em cada iteração de for (int i = 0; i < 10; i++) ?

O problema mais comum com esse comportamento é fazer um fechamento sobre a variável de iteração e tem uma solução fácil:

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

Minha postagem do blog sobre esse problema: Encerra a variável foreach em C # .

Tendo sido mordido por isso, tenho o hábito de include variables ​​definidas localmente no escopo mais interno que uso para transferir para qualquer fechamento. No seu exemplo:

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

Eu faço:

 foreach (var s in strings) { string search = s; query = query.Where(i => i.Prop == search); // New definition ensures unique per iteration. 

Depois de ter esse hábito, você pode evitá-lo no caso muito raro que você pretendia vincular-se aos escopos externos. Para ser sincero, acho que nunca fiz isso.

No c # 5.0, esse problema é corrigido e você pode fechar as variables ​​de loop e obter os resultados esperados.

A especificação da linguagem diz:

8.8.4 A declaração foreach

(…)

Uma declaração foreach do formulário

 foreach (V v in x) embedded-statement 

é então expandido para:

 { E e = ((C)(x)).GetEnumerator(); try { while (e.MoveNext()) { V v = (V)(T)e.Current; embedded-statement } } finally { … // Dispose e } } 

(…)

O posicionamento de v dentro do loop while é importante para como ele é capturado por qualquer function anônima que ocorra na declaração incorporada. Por exemplo:

 int[] values = { 7, 9, 13 }; Action f = null; foreach (var value in values) { if (f == null) f = () => Console.WriteLine("First value: " + value); } f(); 

Se v foi declarado fora do loop while, ele seria compartilhado entre todas as iterações, e seu valor após o loop for seria o valor final, 13 , que é o que a invocação de f imprimiria. Em vez disso, como cada iteração tem sua própria variável v , aquela capturada por f na primeira iteração continuará a conter o valor 7 , que é o que será impresso. ( Nota: versões anteriores do C # declaradas v fora do loop while. )

Na minha opinião, é uma pergunta estranha. É bom saber como o compilador funciona, mas isso é apenas “bom saber”.

Se você escrever código que depende do algoritmo do compilador, isso é uma má prática. E é melhor rewrite o código para excluir essa dependência.

Essa é uma boa pergunta para a entrevista de emprego. Mas na vida real não me deparo com nenhum problema que resolvi em entrevista de emprego.

O 90% de foreach usa para processar cada elemento da coleção (não para selecionar ou calcular alguns valores). às vezes você precisa calcular alguns valores dentro do loop, mas não é uma boa prática criar um loop GRANDE.

É melhor usar expressões LINQ para calcular os valores. Porque quando você calcula muita coisa dentro do loop, depois de 2-3 meses quando você (ou qualquer outra pessoa) vai ler esse código, a pessoa não vai entender o que é isso e como ele deve funcionar.