Quando escrevo código no Visual Studio, o ReSharper (que Deus o abençoe!) Geralmente sugere que eu mude meu loop for old-school da forma mais compacta para foreach.
E, frequentemente, quando aceito essa alteração, o ReSharper dá um passo à frente e sugere que eu a mude novamente, em um formato LINQ brilhante.
Então, eu me pergunto: existem algumas vantagens reais nessas melhorias? Em uma execução de código bastante simples, não consigo ver nenhum aumento de velocidade (obviamente), mas posso ver o código cada vez menos legível ... Então, eu me pergunto: vale a pena?
foreach
é remover itens de uma coleção enquanto a enumera, onde geralmentefor
é necessário um loop para iniciar a partir do último elemento.Respostas:
for
vs.foreach
Existe uma confusão comum de que essas duas construções são muito semelhantes e que ambas são intercambiáveis assim:
e:
O fato de ambas as palavras-chave começarem pelas mesmas três letras não significa que, semanticamente, elas são semelhantes. Essa confusão é extremamente suscetível a erros, especialmente para iniciantes. Iterar através de uma coleção e fazer algo com os elementos é feito com
foreach
;for
não precisa e não deve ser usado para esse fim , a menos que você realmente saiba o que está fazendo.Vamos ver o que há de errado com um exemplo. No final, você encontrará o código completo de um aplicativo de demonstração usado para reunir os resultados.
No exemplo, estamos carregando alguns dados do banco de dados, mais precisamente as cidades da Adventure Works, ordenadas por nome, antes de encontrar "Boston". A seguinte consulta SQL é usada:
Os dados são carregados pelo
ListCities()
método que retorna umIEnumerable<string>
. Aqui está o queforeach
parece:Vamos reescrevê-lo com a
for
, assumindo que ambos sejam intercambiáveis:Ambos retornam as mesmas cidades, mas há uma enorme diferença.
foreach
,ListCities()
é chamado uma vez e produz 47 itens.for
,ListCities()
é chamado 94 vezes e produz 28153 itens no total.O que aconteceu?
IEnumerable
é preguiçoso . Isso significa que ele fará o trabalho somente no momento em que o resultado for necessário. A avaliação preguiçosa é um conceito muito útil, mas possui algumas ressalvas, incluindo o fato de que é fácil perder o (s) momento (s) em que o resultado será necessário, especialmente nos casos em que o resultado é usado várias vezes.No caso de a
foreach
, o resultado é solicitado apenas uma vez. No caso de afor
implementado no código incorretamente escrito acima , o resultado é solicitado 94 vezes , ou seja, 47 × 2:Toda vez que
cities.Count()
é chamado (47 vezes),Toda vez que
cities.ElementAt(i)
é chamado (47 vezes).Consultar um banco de dados 94 vezes em vez de um é terrível, mas não é a pior coisa que pode acontecer. Imagine, por exemplo, o que aconteceria se a
select
consulta fosse precedida por uma consulta que também insere uma linha na tabela. Certo, teríamos ofor
que chamará o banco de dados 2.147.483.647 vezes, a menos que esperemos que ele trava antes.Claro, meu código é tendencioso. Eu deliberadamente usei a preguiça
IEnumerable
e a escrevi de forma a ligar repetidamenteListCities()
. Pode-se notar que um iniciante nunca fará isso, porque:O
IEnumerable<T>
não tem a propriedadeCount
, mas apenas o métodoCount()
. Chamar um método é assustador e pode-se esperar que seu resultado não seja armazenado em cache e não seja adequado em umfor (; ...; )
bloco.A indexação está indisponível
IEnumerable<T>
e não é óbvio encontrar oElementAt
método de extensão LINQ.Provavelmente, a maioria dos iniciantes converteria o resultado
ListCities()
em algo que eles estão familiarizados, como aList<T>
.Ainda assim, esse código é muito diferente da
foreach
alternativa. Novamente, ele fornece os mesmos resultados e, desta vez, oListCities()
método é chamado apenas uma vez, mas gera 575 itens, enquanto que comforeach
ele produz apenas 47 itens.A diferença vem do fato que
ToList()
faz com que todos os dados a ser carregado a partir do banco de dados. Emboraforeach
solicitado apenas nas cidades anteriores a "Boston", o novofor
exige que todas as cidades sejam recuperadas e armazenadas na memória. Com 575 strings curtas, provavelmente não faz muita diferença, mas e se estivéssemos recuperando apenas algumas linhas de uma tabela contendo bilhões de registros?Então, o que é
foreach
realmente?foreach
está mais perto de um loop while. O código que eu usei anteriormente:pode ser simplesmente substituído por:
Ambos produzem a mesma IL. Ambos têm o mesmo resultado. Ambos têm os mesmos efeitos colaterais. Obviamente, isso
while
pode ser reescrito em um infinito semelhantefor
, mas seria ainda mais longo e propenso a erros. Você é livre para escolher o que achar mais legível.Deseja testá-lo você mesmo? Aqui está o código completo:
E os resultados:
LINQ vs. maneira tradicional
Quanto ao LINQ, você pode querer aprender programação funcional (FP) - não coisas de C # FP, mas linguagem FP real, como Haskell. Linguagens funcionais têm uma maneira específica de expressar e apresentar o código. Em algumas situações, é superior aos paradigmas não funcionais.
Sabe-se que o FP é muito superior quando se trata de manipular listas ( lista como um termo genérico, não relacionado a
List<T>
). Dado esse fato, a capacidade de expressar o código C # de uma maneira mais funcional quando se trata de listas é uma coisa bastante boa.Se você não está convencido, compare a legibilidade do código escrito de maneira funcional e não-funcional na minha resposta anterior sobre o assunto.
fonte
foreach
é mais eficiente do quefor
quando, na realidade, a disparidade é resultado de um código deliberadamente quebrado. O rigor da resposta se redime, mas é fácil ver como um observador casual pode chegar a conclusões erradas.Embora já existam ótimas exposições sobre as diferenças entre for e foreach. Existem algumas deturpações grosseiras do papel do LINQ.
A sintaxe do LINQ não é apenas um açúcar sintático, fornecendo uma aproximação de programação funcional ao C #. O LINQ fornece construções funcionais, incluindo todos os benefícios para C #. Combinado com o retorno de IEnumerable em vez de IList, o LINQ fornece execução adiada da iteração. O que as pessoas normalmente fazem agora é construir e retornar um IList de suas funções como
Em vez disso, use a sintaxe de retorno de rendimento para criar uma enumeração adiada.
Agora a enumeração não ocorrerá até você ToList ou iterar sobre ela. E isso ocorre apenas quando necessário (aqui está uma enumeração de Fibbonaci que não tem um problema de estouro de pilha)
A realização de um foreach sobre a função Fibonacci retornará a sequência de 46. Se você quiser o 30º, tudo isso será calculado
Onde nos divertimos muito é o suporte na linguagem para expressões lambda (combinado com as construções IQueryable e IQueryProvider, isso permite a composição funcional de consultas em diversos conjuntos de dados, o IQueryProvider é responsável por interpretar os expressões e criação e execução de uma consulta usando construções nativas da fonte). Não vou entrar nos detalhes detalhados aqui, mas há uma série de postagens no blog mostrando como criar um provedor de consultas SQL aqui
Em resumo, você deve preferir retornar IEnumerable sobre IList quando os consumidores de sua função executarem uma iteração simples. E use os recursos do LINQ para adiar a execução de consultas complexas até que sejam necessárias.
fonte
A legibilidade está nos olhos de quem vê. Algumas pessoas podem dizer
é perfeitamente legível; outros podem dizer que isso é opaco e preferem
como deixar mais claro o que está sendo feito. Não podemos dizer o que você acha mais legível. Mas você pode detectar alguns dos meus próprios preconceitos no exemplo que construí aqui ...
fonte
A diferença entre LINQ e
foreach
realmente se resume a dois estilos de programação diferentes: imperativo e declarativo.Imperativo: nesse estilo, você diz ao computador "faça isso ... agora faça isso ... agora faça isso agora faça isso". Você alimenta um programa um passo de cada vez.
Declarativo: nesse estilo, você diz ao computador qual é o resultado e deixa que ele descubra como chegar lá.
Um exemplo clássico desses dois estilos é comparar o código do assembly (ou C) com o SQL. Na montagem, você fornece instruções (literalmente) uma de cada vez. No SQL, você expressa como unir dados e qual resultado deseja desses dados.
Um bom efeito colateral da programação declarativa é que ela tende a ser um nível um pouco mais alto. Isso permite que a plataforma evolua embaixo de você sem que você precise alterar seu código. Por exemplo:
O que esta acontecendo aqui? O Distinct está usando um núcleo? Dois? Cinquenta? Nós não sabemos e não nos importamos. Os desenvolvedores .NET podem reescrevê-lo a qualquer momento, desde que continuem a executar o mesmo objetivo, nosso código poderia magicamente ficar mais rápido após uma atualização de código.
Este é o poder da programação funcional. E o motivo pelo qual você encontrará esse código em linguagens como Clojure, F # e C # (escrito com uma mentalidade de programação funcional) é geralmente 3x-10x menor do que seus equivalentes imperativos.
Finalmente, eu gosto do estilo declarativo, porque em C # na maioria das vezes isso me permite escrever código que não altera os dados. No exemplo acima,
Distinct()
não muda de barra, ele retorna uma nova cópia dos dados. Isso significa que, qualquer que seja a barra, e de onde ela veio, ela não mudou repentinamente.Assim como os outros pôsteres estão dizendo, aprenda a programação funcional. Isso mudará sua vida. E se você puder, faça-o em uma verdadeira linguagem de programação funcional. Eu prefiro o Clojure, mas F # e Haskell também são excelentes opções.
fonte
var foo = bar.Distinct()
é essencialmente umIEnumerator<T>
até você ligar.ToList()
ou.ToArray()
. Essa é uma distinção importante porque, se você não está ciente disso, pode levar a erros de compreensão difíceis.Outros desenvolvedores da equipe podem ler o LINQ?
Caso contrário, não use ou uma das duas coisas acontecerá:
Um para cada loop é perfeito para percorrer uma lista, mas se não é isso o que você deve fazer, não use um.
fonte