Estou recebendo o seguinte aviso:
Acesso para foreach variável no fechamento. Pode ter um comportamento diferente quando compilado com diferentes versões do compilador.
Isso é o que parece no meu editor:
Eu sei como corrigir esse aviso, mas quero saber por que receberia este aviso?
É sobre a versão "CLR"? Está relacionado a "IL"?
Respostas:
Este aviso tem duas partes. O primeiro é ...
... o que não é inválido per se, mas é contra-intuitivo à primeira vista. Também é muito difícil fazer o certo. (Tanto é verdade que o artigo que link abaixo descreve isso como "prejudicial".)
Faça sua consulta, observando que o código que você extraiu é basicamente uma forma expandida do que o compilador C # (antes do C # 5) gera para
foreach
1 :Bem, é válido sintaticamente. E se tudo o que você está fazendo em seu loop for usar o valor de,
s
então está tudo certo. Mas o fechamentos
levará a um comportamento contra-intuitivo. Dê uma olhada no seguinte código:var countingActions = new List<Action>(); var numbers = from n in Enumerable.Range(1, 5) select n.ToString(CultureInfo.InvariantCulture); using (var enumerator = numbers.GetEnumerator()) { string s; while (enumerator.MoveNext()) { s = enumerator.Current; Console.WriteLine("Creating an action where s == {0}", s); Action action = () => Console.WriteLine("s == {0}", s); countingActions.Add(action); } }
Se você executar este código, obterá a seguinte saída de console:
Creating an action where s == 1 Creating an action where s == 2 Creating an action where s == 3 Creating an action where s == 4 Creating an action where s == 5
Isso é o que você espera.
Para ver algo que você provavelmente não espera, execute o seguinte código imediatamente após o código acima:
foreach (var action in countingActions) action();
Você obterá a seguinte saída de console:
s == 5 s == 5 s == 5 s == 5 s == 5
Por quê? Porque criamos cinco funções que fazem exatamente a mesma coisa: imprimir o valor de
s
(que fechamos). Na realidade, têm a mesma função ("Imprimirs
", "Imprimirs
", "Imprimirs
" ...).No ponto em que vamos usá-los, eles fazem exatamente o que pedimos: imprima o valor de
s
. Se você olhar para o último valor conhecido des
, verá que é5
. Portanto, somoss == 5
impressos cinco vezes no console.Que é exatamente o que pedimos, mas provavelmente não é o que queremos.
A segunda parte do aviso ...
... é o que é. A partir do C # 5, o compilador gera um código diferente que "impede" que isso aconteça via
foreach
.Assim, o código a seguir produzirá resultados diferentes em diferentes versões do compilador:
foreach (var n in numbers) { Action action = () => Console.WriteLine("n == {0}", n); countingActions.Add(action); }
Consequentemente, também produzirá o aviso R # :)
Meu primeiro trecho de código, acima, exibirá o mesmo comportamento em todas as versões do compilador, já que não estou usando
foreach
(em vez disso, expandi-o da maneira que os compiladores pré-C # 5 fazem).Não tenho certeza do que você está perguntando aqui.
A postagem de Eric Lippert diz que a mudança acontece "em C # 5". então
presumivelmente, você deve direcionar o .NET 4.5 ou posteriorcom um compilador C # 5 ou posterior para obter o novo comportamento, e tudo antes disso obtém o comportamento antigo.Mas, para ficar claro, é uma função do compilador e não da versão do .NET Framework.
Código diferente produz IL diferente, portanto, nesse sentido, há consequências para o IL gerado.
1
foreach
é uma construção muito mais comum do que o código que você postou em seu comentário. O problema normalmente surge por meio do uso deforeach
, não por meio da enumeração manual. É por isso que as alteraçõesforeach
no C # 5 ajudam a evitar esse problema, mas não completamente.fonte
foreach
material aqui vem do conteúdo da pergunta. Você está certo ao dizer que isso pode acontecer de várias maneiras mais gerais.A primeira resposta é ótima, então pensei em apenas acrescentar uma coisa.
Você está recebendo o aviso porque, em seu código de exemplo, o modelo refletido está sendo atribuído a um IEnumerable, que só será avaliado no momento da enumeração, e a própria enumeração pode acontecer fora do loop se você atribuir o modelo refletido a algo com um escopo mais amplo .
Se você mudou
...Where(x => x.Name == property.Value)
para
...Where(x => x.Name == property.Value).ToList()
então, ReflectedModel seria atribuído a uma lista definida dentro do loop foreach, então você não receberia o aviso, já que a enumeração definitivamente aconteceria dentro do loop, e não fora dele.
fonte
Uma variável com escopo de bloco deve resolver o aviso.
foreach (var entry in entries) { var en = entry; var result = DoSomeAction(o => o.Action(en)); }
fonte