Filtrando loops foreach com uma condição where vs continuar cláusulas de guarda


24

Eu já vi alguns programadores usarem isso:

foreach (var item in items)
{
    if (item.Field != null)
        continue;

    if (item.State != ItemStates.Deleted)
        continue;

    // code
}

em vez de onde eu normalmente usaria:

foreach (var item in items.Where(i => i.Field != null && i.State != ItemStates.Deleted))
{
    // code
}

Eu já vi uma combinação de ambos. Eu realmente gosto da legibilidade com 'continue', especialmente com condições mais complexas. Existe alguma diferença no desempenho? Com uma consulta ao banco de dados, estou assumindo que existiria. E as listas regulares?


3
Para listas regulares, parece micro otimização.
Apocalypse

2
@ zgnilec: ... mas na verdade qual das duas variantes é a versão otimizada? Eu tenho uma opinião sobre isso, é claro, mas apenas olhando o código, isso não é inerentemente claro para todos.
Doc Brown

2
Claro que continuar será mais rápido. Usando linq .Onde você cria um iterador adicional.
Apocalypse

11
@zgnilec - Boa teoria. Gostaria de postar uma resposta explicando por que você acha isso? Ambas as respostas que existem atualmente dizem o contrário.
Bobson

2
... portanto, a questão é: as diferenças de desempenho entre as duas construções são negligenciáveis ​​e a legibilidade e a depuração podem ser alcançadas para ambas. É simplesmente uma questão de gosto, qual você prefere.
Doc Brown

Respostas:


63

Eu consideraria isso um local apropriado para usar a separação de comando / consulta . Por exemplo:

// query
var validItems = items.Where(i => i.Field != null && i.State != ItemStates.Deleted);
// command
foreach (var item in validItems) {
    // do stuff
}

Isso também permite que você dê um bom nome de auto-documentação ao resultado da consulta. Também ajuda a ver oportunidades de refatoração, porque é muito mais fácil refatorar o código que apenas consulta dados ou apenas modifica dados do que o código misto que tenta fazer as duas coisas.

Ao depurar, você pode interromper antes foreachpara verificar rapidamente se o conteúdo da validItemsresolução está conforme o esperado. Você não precisa entrar no lambda, a menos que precise. Se você precisar entrar no lambda, sugiro fatorá-lo em uma função separada e, em seguida, faça isso.

Existe alguma diferença no desempenho? Se a consulta é apoiada por um banco de dados, então a versão LINQ tem o potencial para correr mais rápido, porque a consulta SQL pode ser mais eficiente. Se for LINQ to Objects, você não verá nenhuma diferença real de desempenho. Como sempre, analise seu código e corrija os gargalos que são realmente relatados, em vez de tentar prever otimizações com antecedência.


11
Por que um conjunto de dados extremamente grande faria diferença? Só porque o custo minúsculo das lambdas acabaria aumentando?
BlueRaja - Danny Pflughoeft

11
@ BlueRaja-DannyPflughoeft: Sim, você está certo, este exemplo não envolve complexidade algorítmica adicional além do código original. Eu removi a frase.
Christian Hayter

Isso não resulta em duas iterações sobre a coleção? Naturalmente, o segundo é mais curto, considerando que apenas itens válidos estão nele, mas você ainda precisa fazer isso duas vezes, uma vez para filtrar os elementos e o segundo para trabalhar com os itens válidos.
Andy

11
@ DavidPacker Não. O IEnumerableestá sendo conduzido apenas pelo foreachloop.
Benjamin Hodgson

2
@ DavidPacker: Isso é exatamente o que faz; a maioria dos métodos LINQ to Objects são implementados usando blocos iteradores. O código de exemplo acima irá percorrer a coleção exatamente uma vez, executando o Wherelambda e o corpo do loop (se o lambda retornar true) uma vez por elemento.
Christian Hayter

7

Obviamente, há uma diferença no desempenho, .Where()resultando em uma chamada de delegado para cada item. No entanto, eu não me preocuparia com o desempenho:

  • Os ciclos de relógio usados ​​na chamada de um delegado são insignificantes em comparação com os ciclos de relógio usados ​​pelo restante do código que itera sobre a coleção e verifica as condições.

  • A penalidade de desempenho de chamar um delegado é da ordem de alguns ciclos de relógio e, felizmente, já passamos dos dias em que precisávamos nos preocupar com ciclos de relógio individuais.

Se, por algum motivo, o desempenho for realmente importante para você no nível do ciclo do relógio, use em List<Item>vez de IList<Item>, para que o compilador possa fazer uso de chamadas diretas (e inlináveis) em vez de chamadas virtuais e para que o iterador de List<T>, na verdade, seja a struct, não precisa ser encaixotado. Mas isso é realmente insignificante.

Uma consulta ao banco de dados é uma situação diferente, porque existe (pelo menos em teoria) a possibilidade de enviar o filtro para o RDBMS, melhorando consideravelmente o desempenho: somente as linhas correspondentes farão a viagem do RDBMS para o seu programa. Mas, para isso, acho que você precisaria usar o linq, não acho que essa expressão possa ser enviada ao RDBMS como está.

Você realmente verá os benefícios do if(x) continue;momento em que precisa depurar esse código: Passar por cima de if()s e continues funciona muito bem; entrar no delegado de filtragem é uma dor.


É quando algo está errado e você deseja examinar todos os itens e verificar no depurador quais têm Field! = Null e quais têm State! = Null; isso pode ser difícil ou impossível com foreach ... onde.
precisa saber é o seguinte

Bom ponto com depuração. Entrar em um local não é tão ruim assim no Visual Studio, mas você não pode reescrever expressões lambda durante a depuração sem recompilar, o que você evita ao usar if(x) continue;.
Paprik 07/12/2015

A rigor, .Wheresó é invocado uma vez. O que é invocado em cada iteração é o delegado filtro (e MoveNexte Currentsobre o recenseador, quando eles não são otimizados para fora)
CodesInChaos

@CodesInChaos demorei um pouco para pensar sobre o que você está falando, mas é claro que wh00ps, você está certo, a rigor, .Wheresó é invocado uma vez. Corrigido.
Mike Nakis
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.