Aparentemente, existem várias maneiras de interagir com uma coleção. Curioso se houver alguma diferença, ou por que você usaria um caminho sobre o outro.
Primeiro tipo:
List<string> someList = <some way to init>
foreach(string s in someList) {
<process the string>
}
Outro jeito:
List<string> someList = <some way to init>
someList.ForEach(delegate(string s) {
<process the string>
});
Suponho que, em vez de um delegado anônimo que eu uso acima, você tenha um delegado reutilizável que possa especificar ...
Respostas:
Há uma distinção importante e útil entre os dois.
Como o .ForEach usa um
for
loop para iterar a coleção, isso é válido (edit: anterior ao .net 4.5 - a implementação foi alterada e os dois lançaram):Considerando que
foreach
usa um enumerador, então isso não é válido:tl; dr: NÃO copypaste este código no seu aplicativo!
Esses exemplos não são práticas recomendadas, são apenas para demonstrar as diferenças entre
ForEach()
eforeach
.Remover itens de uma lista dentro de um
for
loop pode ter efeitos colaterais. O mais comum é descrito nos comentários a esta pergunta.Geralmente, se você deseja remover vários itens de uma lista, você deseja separar a determinação de quais itens remover da remoção real. Ele não mantém seu código compacto, mas garante que você não perca nenhum item.
fonte
Tínhamos algum código aqui (no VS2005 e C # 2.0) onde os engenheiros anteriores se esforçavam para usar em
list.ForEach( delegate(item) { foo;});
vez deforeach(item in list) {foo; };
todo o código que eles escreviam. por exemplo, um bloco de código para ler linhas de um dataReader.Ainda não sei exatamente por que eles fizeram isso.
As desvantagens de
list.ForEach()
são:É mais detalhado no C # 2.0. No entanto, no C # 3 em diante, você pode usar a
=>
sintaxe " " para criar expressões bem concisas.É menos familiar. As pessoas que precisam manter esse código se perguntam por que você fez dessa maneira. Levei um tempo para decidir que não havia motivo, exceto talvez para fazer o escritor parecer inteligente (a qualidade do restante do código prejudicou isso). Também era menos legível, com o "
})
" no final do bloco de código delegado.Veja também o livro de Bill Wagner "C # eficaz: 50 maneiras específicas de melhorar seu C #", onde ele fala sobre por que o foreach é preferido a outros loops como for ou while - o ponto principal é que você está deixando o compilador decidir a melhor maneira de construir o laço. Se uma versão futura do compilador conseguir usar uma técnica mais rápida, você a receberá gratuitamente usando foreach e reconstrução, em vez de alterar seu código.
uma
foreach(item in list)
construção permite que você usebreak
oucontinue
se precisar sair da iteração ou do loop. Mas você não pode alterar a lista dentro de um loop foreach.Estou surpreso ao ver que
list.ForEach
é um pouco mais rápido. Mas esse provavelmente não é um motivo válido para usá-lo, seria otimização prematura. Se o seu aplicativo usar um banco de dados ou serviço da Web que, não o controle de loop, quase sempre será onde o tempo passa. E você também comparou isso com umfor
loop? Alist.ForEach
poderia ser mais rápido devido ao uso que, internamente e umfor
laço sem o invólucro seria ainda mais rápido.Não concordo que a
list.ForEach(delegate)
versão seja "mais funcional" de forma significativa. Ele passa uma função para uma função, mas não há grande diferença no resultado ou na organização do programa.Eu não acho que
foreach(item in list)
"diz exatamente como você quer que seja feito" - umfor(int 1 = 0; i < count; i++)
loop faz isso, umforeach
loop deixa a escolha do controle até o compilador.Meu sentimento é, em um novo projeto, usar
foreach(item in list)
a maioria dos loops para aderir ao uso comum e à legibilidade, e usarlist.Foreach()
apenas para blocos curtos, quando você pode fazer algo mais elegante ou compacto com o=>
operador C # 3 " ". Em casos como esse, já pode haver um método de extensão LINQ mais específico queForEach()
. Veja seWhere()
,Select()
,Any()
,All()
,Max()
ou um dos muitos outros métodos LINQ não já fazer o que quiser a partir do loop.fonte
Por diversão, coloquei List no refletor e este é o C # resultante:
Da mesma forma, o MoveNext no Enumerador que é usado pelo foreach é o seguinte:
O List.ForEach é muito mais reduzido do que o MoveNext - muito menos processamento - provavelmente JIT em algo eficiente.
Além disso, foreach () alocará um novo enumerador, não importa o quê. O GC é seu amigo, mas se você estiver fazendo o mesmo repetidamente, isso criará mais objetos descartáveis, em vez de reutilizar o mesmo delegado - MAS - esse é realmente um caso marginal. No uso típico, você verá pouca ou nenhuma diferença.
fonte
Conheço duas coisas obscuras que as tornam diferentes. Vai eu!
Em primeiro lugar, existe o bug clássico de criar um delegado para cada item da lista. Se você usar a palavra-chave foreach, todos os seus representantes poderão acabar se referindo ao último item da lista:
O método List.ForEach não tem esse problema. O item atual da iteração é passado por valor como um argumento para o lambda externo e, em seguida, o lambda interno captura corretamente esse argumento em seu próprio fechamento. Problema resolvido.
(Infelizmente, acredito que o ForEach é membro da List, e não um método de extensão, embora seja fácil defini-lo para que você tenha esse recurso em qualquer tipo enumerável.)
Em segundo lugar, a abordagem do método ForEach tem uma limitação. Se você estiver implementando IEnumerable usando retorno de rendimento, não poderá fazer um retorno de rendimento dentro do lambda. Portanto, percorrer os itens de uma coleção para gerar retornos não é possível por esse método. Você precisará usar a palavra-chave foreach e contornar o problema de fechamento fazendo uma cópia manualmente do valor atual do loop dentro do loop.
Mais aqui
fonte
foreach
é "corrigido" no C # 5. stackoverflow.com/questions/8898925/…Acho que a
someList.ForEach()
chamada pode ser facilmente paralelizada, enquanto o normalforeach
não é tão fácil de executar em paralelo. Você poderia executar facilmente vários delegados diferentes em núcleos diferentes, o que não é tão fácil de fazer com um normalforeach
.Apenas meus 2 centavos
fonte
Como se costuma dizer, o diabo está nos detalhes ...
A maior diferença entre os dois métodos de enumeração de coleção é que
foreach
carrega state, enquantoForEach(x => { })
que não.Mas vamos nos aprofundar um pouco mais, porque há algumas coisas que você deve estar ciente que podem influenciar sua decisão e há algumas ressalvas que você deve estar ciente ao codificar para qualquer um dos casos.
Vamos usar
List<T>
em nosso pequeno experimento para observar o comportamento. Para esta experiência, estou usando o .NET 4.7.2:Permite iterar sobre isso com o
foreach
primeiro:Poderíamos expandir isso para:
Com o enumerador em mãos, olhando embaixo das cobertas, obtemos:
Duas coisas se tornam imediatamente evidentes:
Naturalmente, isso não é de forma alguma seguro para threads. Como foi apontado acima, alterar a coleção durante a iteração é apenas um mojo ruim.
Mas e o problema de a coleção se tornar inválida durante a iteração por meios fora de nós que mexem com a coleção durante a iteração? As práticas recomendadas sugerem a versão da coleção durante operações e iteração e a verificação de versões para detectar quando a coleção subjacente é alterada.
Aqui é onde as coisas ficam realmente sombrias. De acordo com a documentação da Microsoft:
Bem, o que isso significa? A título de exemplo, apenas porque
List<T>
implementa o tratamento de exceções não significa que todas as coleções que implementamIList<T>
farão o mesmo. Isso parece ser uma clara violação do Princípio da Substituição de Liskov:Outro problema é que o enumerador deve implementar
IDisposable
- isso significa outra fonte de vazamentos de memória em potencial, não apenas se o chamador errar, mas se o autor não implementar oDispose
padrão corretamente.Por fim, temos um problema vitalício ... o que acontece se o iterador for válido, mas a coleção subjacente desaparecer? Agora, temos um instantâneo do que era ... quando você separa a vida útil de uma coleção e seus iteradores, você está pedindo problemas.
Vamos agora examinar
ForEach(x => { })
:Isso se expande para:
De nota importante é o seguinte:
for (int index = 0; index < this._size && ... ; ++index) action(this._items[index]);
Esse código não aloca nenhum enumerador (nada a
Dispose
) e não pausa durante a iteração.Observe que isso também executa uma cópia superficial da coleção subjacente, mas a coleção agora é um instantâneo no tempo. Se o autor não implementar corretamente uma verificação para a coleção mudar ou ficar obsoleta, o instantâneo ainda será válido.
Isso não protege de maneira alguma o problema dos problemas da vida útil ... se a coleção subjacente desaparecer, agora você terá uma cópia superficial que aponta para o que era ... mas pelo menos você não tem uma
Dispose
problemas para lidar com iteradores órfãos ...Sim, eu disse iteradores ... às vezes é vantajoso ter estado. Suponha que você queira manter algo semelhante a um cursor de banco de dados ... talvez vários
foreach
estilosIterator<T>
sejam o caminho a seguir. Pessoalmente, não gosto desse estilo de design, pois há muitos problemas ao longo da vida e você confia nas boas graças dos autores das coleções nas quais está confiando (a menos que você literalmente escreva tudo do zero).Sempre há uma terceira opção ...
Não é sexy, mas tem dentes (desculpas a Tom Cruise e ao filme The Firm )
É sua escolha, mas agora você sabe e pode ser informado.
fonte
Você pode nomear o delegado anônimo :-)
E você pode escrever o segundo como:
O que eu prefiro e economiza muita digitação.
Como Joachim diz, o paralelismo é mais fácil de aplicar à segunda forma.
fonte
Nos bastidores, o delegado anônimo é transformado em um método real, para que você possa ter alguma sobrecarga com a segunda opção se o compilador não optar por incorporar a função. Além disso, quaisquer variáveis locais referenciadas pelo corpo do exemplo de delegado anônimo mudariam de natureza devido a truques do compilador para ocultar o fato de que ela é compilada para um novo método. Mais informações aqui sobre como o C # faz essa mágica:
http://blogs.msdn.com/oldnewthing/archive/2006/08/04/688527.aspx
fonte
A função ForEach é membro da classe genérica List.
Eu criei a seguinte extensão para reproduzir o código interno:
Então, no final, estamos usando um foreach normal (ou um loop, se você quiser).
Por outro lado, o uso de uma função delegada é apenas outra maneira de definir uma função, este código:
é equivalente a:
ou usando expressões labda:
fonte
Todo o escopo ForEach (delegar função) é tratado como uma única linha de código (chamando a função) e você não pode definir pontos de interrupção nem entrar no código. Se ocorrer uma exceção não tratada, o bloco inteiro será marcado.
fonte
List.ForEach () é considerado mais funcional.
List.ForEach()
diz o que você quer fazer.foreach(item in list)
também diz exatamente como você quer que seja feito. Isso deixaList.ForEach
livre para alterar a implementação da parte como no futuro. Por exemplo, uma versão futura hipotética do .Net sempre pode ser executadaList.ForEach
em paralelo, supondo que neste momento todos tenham um número de núcleos de CPU que geralmente estão ociosos.Por outro lado,
foreach (item in list)
oferece um pouco mais de controle sobre o loop. Por exemplo, você sabe que os itens serão iterados em algum tipo de ordem seqüencial e poderá facilmente interromper no meio se um item atender a alguma condição.Algumas observações mais recentes sobre esse assunto estão disponíveis aqui:
fonte
A segunda maneira que você mostrou usa um método de extensão para executar o método delegado para cada um dos elementos da lista.
Dessa forma, você tem outra chamada de delegado (= método).
Além disso, existe a possibilidade de iterar a lista com um loop for .
fonte
Uma coisa a ser cautelosa é como sair do método .ForEach genérico - consulte esta discussão . Embora o link pareça dizer que esse caminho é o mais rápido. Não sei por que - você pensaria que eles seriam equivalentes uma vez compilados ...
fonte
Existe uma maneira, eu fiz isso no meu aplicativo:
Você pode usar o item como do foreach
fonte