Se eu usar:
var strings = new List<string> { "sample" };
foreach (string s in strings)
{
Console.WriteLine(s);
strings.Add(s + "!");
}
o Add
in foreach
lança uma InvalidOperationException (Collection foi modificada; a operação de enumeração pode não ser executada), que considero lógico, já que estamos puxando o tapete debaixo de nossos pés.
No entanto, se eu usar:
var strings = new List<string> { "sample" };
strings.ForEach(s =>
{
Console.WriteLine(s);
strings.Add(s + "!");
});
ele prontamente atira no próprio pé fazendo um loop até que lance uma OutOfMemoryException.
Isso é uma surpresa para mim, pois sempre pensei que List.ForEach era apenas um invólucro para foreach
ou para for
.
Alguém tem uma explicação para como e por que esse comportamento?
(Inspirado por ForEach loop para uma lista genérica repetida indefinidamente )
foreach
ou parafor
." Ainda pode usarfor
. Você pode executar a mesma ação em umfor
loop e gerar a mesma OutOfMemoryException como resultado.Respostas:
É porque o
ForEach
método não usa o enumerador, ele percorre os itens com umfor
loop:(código obtido com JustDecompile)
Como o enumerador não é usado, ele nunca verifica se a lista foi alterada e a condição final do
for
loop nunca é alcançada porque_size
é aumentada a cada iteração.fonte
_size
calculado? Se for apenas pré-calculado, deve ser executado apenas uma vez para o meu exemplo. É obviamente atualizado de alguma forma._version
variável privada emList<T>
que pode detectar este tipo de cenário, pois é atualizada nas operações que alteram a própria lista.List<T>.ForEach
é implementado através defor
dentro, por isso não usa enumerador e permite modificar a coleção.fonte
Porque o ForEach anexado à classe List usa internamente um loop for que é anexado diretamente a seus membros internos - que você pode ver baixando o código-fonte do .NET framework.
http://referencesource.microsoft.com/netframework.aspx
Onde, como um loop foreach, é antes de tudo uma otimização do compilador, mas também deve operar contra a coleção como um observador - então, se a coleção for modificada, ele lançará uma exceção.
fonte
Add
linhastrings.Insert(0, s + "!")
apenas imprime 'amostra'. É estranho que isso não seja mencionado na documentação.Nós sabemos sobre este problema, foi um descuido quando foi originalmente escrito. Infelizmente, não podemos alterá-lo porque agora impediria a execução desse código que funcionava anteriormente:
A utilidade desse método em si é questionável, como Eric Lippert apontou, então não o incluímos para .NET para aplicativos estilo Metro (ou seja, aplicativos do Windows 8).
David Kean (Equipe BCL)
fonte