Menu

LINQ em C#: Where, Select, OrderBy, GroupBy e mais

O LINQ adiciona operadores de consulta a toda coleção em C#. Veja a sintaxe de métodos e a sintaxe de consulta, filtrar com Where, projetar com Select, ordenar com OrderBy e ThenBy, agrupar com GroupBy, a armadilha de First vs FirstOrDefault, agregações e como a execução adiada muda o momento em que a consulta executa.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

O LINQ (Language Integrated Query) é uma biblioteca de métodos para filtrar, transformar, ordenar, agrupar e resumir sequências. Adicione using System.Linq; e todo array, List<T>, Dictionary e outro IEnumerable<T> ganha métodos como Where, Select e OrderBy, cada um recebendo uma lambda que diz o que fazer com um elemento.

Uma primeira consulta: sintaxe de métodos e sintaxe de consulta

O LINQ tem duas notações. A sintaxe de métodos encadeia chamadas; a sintaxe de consulta se parece com SQL. O compilador transforma a sintaxe de consulta nas mesmas chamadas de métodos, então elas produzem resultados idênticos.

Saída:

95, 91, 88
95, 91, 88

A sintaxe de métodos é mais comum em código moderno, e alguns operadores (Take, Distinct, Count, First, ToList) só existem como métodos. A sintaxe de consulta brilha em joins e em consultas que introduzem variáveis intermediárias com let. O resto desta página usa a sintaxe de métodos e mostra a sintaxe de consulta onde ela acrescenta algo.

Where e Select: filtrar e projetar

Where mantém os elementos que atendem a uma condição. Select transforma cada elemento em outra coisa. Juntos, eles cobrem a maioria das consultas do dia a dia.

Saída:

Mug, Kettle
MUG $8.50 | LAMP $32.00 | KETTLE $27.00 | RUG $120.00
Lamp: 38.40
Rug: 144.00
1. Mug 2. Lamp 3. Kettle 4. Rug

Where nunca muda os elementos, e Select nunca muda quantos eles são. new { p.Name, WithTax = ... } cria um tipo anônimo, uma classe que o compilador escreve para você, cujas propriedades você lê com a sintaxe de ponto normal. Ele não tem um nome que você possa escrever, então uma variável que guarda um precisa ser declarada com var, e ele serve para resultados que ficam dentro de um método.

OrderBy, ThenBy e ordenação por várias chaves

Saída:

Ana 60
Ana 15
Ben 90
Ben 40
Chloe 25
Ben, Ana, Ben, Chloe, Ana

OrderBy é estável: elementos com chaves iguais ficam na ordem original. Use ThenBy ou ThenByDescending para uma segunda chave. Um segundo OrderBy reordenaria tudo pela própria chave, deixando a primeira chave só como desempate: a prioridade oposta. Na sintaxe de consulta, as chaves são separadas por vírgula: orderby o.Customer, o.Total descending.

Ao contrário de List<T>.Sort, OrderBy não mexe na origem e retorna uma nova sequência.

First, Single, Last e as versões OrDefault

Estes retornam um elemento em vez de uma sequência. As versões simples lançam exceção quando não há nada para retornar; as versões OrDefault retornam o valor padrão do tipo.

Saída:

Ana
Ben
Bea
True
First: InvalidOperationException
Single: InvalidOperationException
0

A exceção de First em uma sequência vazia é InvalidOperationException com a mensagem "Sequence contains no elements" (ou "Sequence contains no matching element" quando um predicado não encontrou nada), um dos erros de C# mais pesquisados. Use FirstOrDefault quando "nenhum" é um resultado normal e verifique se é null.

A última linha mostra a armadilha dos tipos de valor: para uma sequência de int, FirstOrDefault retorna 0 quando nada corresponde, o que não dá para distinguir de um 0 de verdade. Filtre com Where e verifique Any() antes, ou no .NET 6 em diante passe o seu próprio padrão: scores.FirstOrDefault(s => s > 100, -1).

Single é para "exatamente um": lança exceção se não houver nenhum ou houver mais de um, o que o torna tanto uma afirmação quanto uma consulta.

Any, All, Count e Contains

Saída:

True
True
True
3
True
True

Any() para na primeira correspondência, então items.Any() é a forma certa de perguntar "existe alguma coisa?". items.Count() > 0 lê a propriedade Count quando a origem é uma coleção como uma lista ou um array, mas em uma consulta ou outra sequência preguiçosa ele percorre todos os elementos antes. All em uma sequência vazia retorna true, porque não existe elemento que falhe na condição.

Sum, Average, Min e Max

Saída:

Total: 28.25
Items: 7
Priciest: 7.25
Average price: 4.92
0
Average: InvalidOperationException

Sum de uma sequência vazia é 0, mas Average, Min e Max de uma sequência vazia de números lançam InvalidOperationException: não existe uma resposta razoável. Proteja com Any(), ou use DefaultIfEmpty() antes: empty.DefaultIfEmpty(0).Average().

Distinct, Take e Skip

Saída:

home, shop, cart
Page 1: 1 2 3 4 5 6 7 8 9 10
Page 2: 11 12 13 14 15 16 17 18 19 20
Page 3: 21 22 23
12, 15

Distinct mantém a primeira ocorrência de cada valor, em ordem. Skip(n).Take(m) é o padrão clássico de paginação, e TakeWhile/SkipWhile param ou começam no primeiro elemento que falha em uma condição. Enumerable.Range(start, count) gera uma sequência de inteiros sem um array.

GroupBy

GroupBy distribui os elementos em grupos por uma chave. Cada grupo tem uma Key e é em si uma sequência, então você pode agregá-lo.

Saída:

North: 3 sales, total 600
South: 2 sales, total 400
East: 1 sales, total 350
Top rep: Ana 400
North -> Ana/Chloe/Ana; South -> Ben/Eli; East -> Dev

Os grupos saem na ordem em que as chaves aparecem pela primeira vez na origem (North, South, East), não ordenados; adicione OrderBy(g => g.Key) se precisar disso. Na sintaxe de consulta, group X by K escolhe o que entra em cada grupo (aqui só o nome do vendedor) e into g dá nome ao grupo para que a consulta possa continuar.

ToList, ToArray e ToDictionary

Os métodos To... executam a consulta e guardam os resultados.

Saída:

ben@x.com
2
ToDictionary: ArgumentException

ToDictionary lança ArgumentException quando dois elementos produzem a mesma chave, igual a Dictionary.Add. Se duplicatas são esperadas, agrupe antes (como wordCounts faz) ou use ToLookup, que mapeia cada chave para uma sequência de valores.

Execução adiada

Where, Select, OrderBy, GroupBy e a maioria dos outros operadores retornam um objeto de consulta e não fazem nenhum trabalho até que algo o enumere. Isso tem duas consequências que vale a pena ver:

Saída:

25, 40, 99
  converting 10
  converting 25
  converting 40
  converting 99
Count: 4
  converting 10
  converting 25
  converting 40
  converting 99
Max: 9900
  converting 10
  converting 25
  converting 40
  converting 99
Cached: 4, 9900

A primeira consulta viu o 99 mesmo ele tendo sido adicionado depois de a consulta ser escrita: o filtro executou quando string.Join a enumerou. A segunda consulta imprimiu suas conversões duas vezes porque Count() e Max() a enumeraram do zero cada um. Com uma lambda barata isso é só trabalho desperdiçado; com uma consulta a banco de dados, uma leitura de arquivo ou um número aleatório dentro da lambda, as duas passadas podem retornar dados diferentes. Quando você vai usar um resultado mais de uma vez, materialize-o com ToList() ou ToArray().

Operadores que retornam um único valor (Count, Sum, First, Any, Max) e os métodos To... executam imediatamente. Veja IEnumerable e yield para entender como essa preguiça é construída.

Referência rápida

OperadorFazRetorna
Where(x => cond)FiltraSequência (adiada)
Select(x => expr)Transforma cada umSequência (adiada)
SelectMany(x => seq)Achata sequências aninhadasSequência (adiada)
OrderBy, ThenBy (+Descending)Ordena, de forma estávelSequência (adiada)
GroupBy(x => key)Agrupa por chaveSequência de grupos (adiada)
Distinct, Take, SkipRemove duplicados, paginaSequência (adiada)
First, Single, LastUm elemento, lança exceção se não houverElemento
FirstOrDefault etc.Um elemento ou o padrãoElemento
Any, All, ContainsTestabool
Count, Sum, Min, Max, AverageAgregaNúmero
ToList, ToArray, ToDictionaryExecuta e guardaColeção

Erros comuns

  • First() em uma sequência que pode estar vazia. Lança "Sequence contains no elements"; use FirstOrDefault e verifique.
  • Duas chamadas a OrderBy. A segunda chave vira a principal; use ThenBy.
  • Enumerar uma consulta várias vezes. O trabalho se repete; chame ToList() uma vez.
  • Esperar que uma consulta tire uma foto da origem. Ela lê a origem quando é enumerada, não quando é escrita.
  • Average, Min, Max em uma sequência vazia. Eles lançam exceção; proteja com Any() ou DefaultIfEmpty.
  • Count() > 0 para testar se há elementos. Any() para no primeiro.

Perguntas frequentes

O que é LINQ em C#?

LINQ (Language Integrated Query) é um conjunto de métodos de extensão em System.Linq, como Where, Select, OrderBy e GroupBy, que funcionam em qualquer IEnumerable<T>: arrays, listas, dicionários e resultados de outras consultas. C# também tem uma sintaxe de consulta (from x in items where ... select ...) que o compilador traduz para as mesmas chamadas de métodos.

Qual a diferença entre Select e Where no LINQ?

Where filtra: mantém os elementos para os quais a lambda retorna true e não os altera. Select projeta: transforma cada elemento em outra coisa, como uma propriedade ou um objeto novo, e mantém a mesma quantidade. Eles costumam ser encadeados: people.Where(p => p.Age >= 18).Select(p => p.Name).

Qual a diferença entre First e FirstOrDefault?

First() retorna o primeiro elemento (ou a primeira correspondência de um predicado) e lança InvalidOperationException ("Sequence contains no elements" ou "no matching element") quando não há nenhum. FirstOrDefault() retorna o valor padrão do tipo no lugar: null para classes e 0 para int, então verifique o resultado antes de usá-lo.

Como ordenar por dois campos com LINQ?

Encadeie ThenBy depois de OrderBy: orders.OrderBy(o => o.Customer).ThenByDescending(o => o.Total). Chamar OrderBy duas vezes não funciona: a segunda chamada reordena a sequência inteira, então a chave dela vira a principal e a primeira chave só desempata, o contrário do que se queria.

Como funciona o GroupBy no LINQ?

GroupBy(x => key) retorna um grupo por chave distinta. Cada grupo é um IGrouping<TKey, TElement>: ele tem uma propriedade Key e é em si uma sequência dos elementos com essa chave, então você pode chamar Count(), Sum() ou Select nele. Os grupos saem na ordem em que as chaves aparecem pela primeira vez.

O que é execução adiada no LINQ?

A maioria dos operadores LINQ não executa quando você os chama; eles retornam uma consulta que executa toda vez que é enumerada (por foreach, ToList(), Count() e assim por diante). Por isso, uma consulta vê mudanças feitas na origem depois de ter sido definida, e enumerá-la duas vezes faz o trabalho duas vezes. Chame ToList() ou ToArray() para executá-la uma vez e guardar os resultados.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR