.NET 6 -  Novas extensões da LINQ


 Hoje vou apresentar as novas extensões da LINQ incluídas no .NET 6.

A LINQ recebeu alguma atenção durante o desenvolvimento do .NET 6 e várias novas extensões foram adicionadas.

Esses recursos foram incluídos no .NET 6 preview 4, portanto, para usar essas novas extensões, você precisa instalar esta versão do .NET 6 ou mais recente. E para ter uma  melhor experiência do desenvolvedor, você também precisa da versão mais recente do Visual Studio 2022.

1- Suporte de índice com ElementAt

O C# 8 introduziu índices que podem ser usados para indexar em listas e matrizes a partir do final, como:

var lista = new string[] { "Net6", "LINQ", "2021", "Novidades", "VS 2022"};
Console.WriteLine(lista[^1]); //Exibe 'VS 2022'

 

Veja o meu artigo - C# 8.0  -  Apresentando Indices e Ranges para mais detalhes.

Os índices eram exclusivos para coleções como arrays e listas e isso faz sentido, pois é fácil encontrar o enésimo elemento no final quando você sabe o tamanho de uma coleção.

Mas agora, também podemos fazer isso para qualquer coleção IEnumerable usando ElementAt(Index index).

Para isso é feita uma verificação se o tipo subjacente tem um tamanho fixo, e se disponível, ele será usado. Internamente é usado o método TryGetNonEnumeratedCount para tentar obter a contagem sem enumerar.

Assim para obter algo do final de uma coleção, antes você tinha que calcular o comprimento da coleção e, em seguida, obter o item por esse índice, subtraindo conforme necessário:

 

   var ultimoElemento = lista.ElementAt(lista.Count() - 1);     

 

Com o .NET 6, a LINQ agora permite que você use uma sobrecarga de índice para o método ElementAt da seguinte maneira:

 

   var ultimoElemento = lista.ElementAt(^1);     

 

Assim estamos usando os recursos já existente no C# para capturar elementos a partir do final de uma coleção.

2- Take com suporte a Range

Com base na ideia anterior de usar índices para melhorar o código, o método Take foi expandido para funcionar com um parâmetro Range:

 

   var intervalo1 = lista.Take(2..3);     
   var intervalo2 = lista.Take(2..);     
   var intervalo3 = lista.Take(..3);     

 

Este código  pula até o 2º elemento e, em seguida, pega as 3 entradas seguintes.

Outras expressões de intervalo, como ..3 ou 2.. também funcionam, o que expande significativamente sua capacidade de capturar elementos de coleções por índices específicos.

3- Sobrecarga do parâmetro Zip

Anteriormente, o LINQ oferecia um método Zip que permitia enumerar por meio de duas coleções em paralelo:

string[] titulos = { "Missão Impossivel", "O quarto da morte", "Orgulho e preconceito" };
string[] generos = { "Drama", "Horror", "Romance" };

foreach ((string titulo, string genero) in titulos.Zip(generos))
{
    Console.WriteLine($"{titulo} é um filme de : {genero}\n");
}

Resultado:

Esse recurso é fantástico pois evita que tenhamos que criar tipos anônimos ou nomeados adicionais para percorrer várias coleções.

No entanto, existem alguns casos em que você pode desejar enumerar por meio de três coleções juntas.

Para atender a essa necessidade, a LINQ agora tem uma sobrecarga adicional que permite que três coleções trabalhem em conjunto:

string[] titulos = { "Missão Impossivel", "O quarto da morte", "Orgulho e preconceito" };
string[] generos = { "Drama", "Horror", "Romance" };
float[] avaliacoes = { 5f, 3.5f, 4.5f };

foreach ((string titulo, string genero, float avaliacao) in titulos.Zip(generos, avaliacoes))
{
    Console.WriteLine($"{titulo} é um filme de : {genero}\n que obteve a avaliação : {avaliacao}");
}

Console.Read();

Resultado:

Aqui temos que um é normal , dois é bom e três é melhor...

4- Parâmetros padrão para métodos comuns

Quem nunca usou os métodos FirstOrDefault, SingleOrDefault e LastOrDefault da LINQ ? Eles são como pilares da LINQ.

Esses métodos examinarão uma coleção e retornarão uma correspondência se uma condição for atendida.

Se uma condição não for atendida, o valor padrão para esse tipo será usado.

  1. Para tipos de referência serão null;
  2. Os tipos numéricos usarão o valor 0;
  3. Os booleanos usarão false;

Por exemplo, digamos que vamos tentar obter o primeiro filme que incluiu um autor em seu elenco:


  
Filme filme = filmes.FirstOrDefault(m => m.Elenco.Includes("Macoratti"));   
 

Como eu nunca estive em um filme, FirstOrDefault iria retornar o seu valor padrão e o filme seria definido como null.

No entanto, no .NET 6, a LINQ agora permite que você especifique um parâmetro personalizado a ser usado caso nada corresponda à condição.

Isso evita ter que lidar com valores nulos e, em vez disso, especifica uma alternativa segura, conforme ilustra o código a seguir:


  
Filme filme = filmes.FirstOrDefault(m => m.Elenco.Includes("Macoratti"), valorPadrao);   
 

Nesse caso, quando FirstOrDefault não atingir uma correspondência, ele usará o parâmetro valorPadrao.

Essa mudança também se aplica a SingleOrDefault e LastOrDefault de maneiras semelhantes, onde agora você pode especificar um parâmetro valorPadrao personalizado.

A seguir temos um exemplo bem simples onde temos uma lista de números e vamos retornar -1 caso FirstOrDefault não encontre a correspondência pretendida:

List<int> numeros = new List<int> { 1, 2, 3, 4, 5 };
var resultado = numeros.FirstOrDefault(x => x > 5, -1);

5- MaxBy and MinBy

Finalmente, o .NET 6 oferece os métodos de extensão MinBy e MaxBy na LINQ.

Esses dois métodos permitem que você percorra a sua coleção e encontre o maior ou o menor de algo, com base em uma expressão lambda  fornecida por você.

Antes do .NET 6, para obter a entidade que tinha o maior ou o menor de algo, você teria que usar Max ou Min para encontrar o valor e, em seguida, consultar novamente a coleção para encontrar a entidade relacionada:

int numBatalhas = filmes.Max(m => m.BatalhasEspaciais);

Filme ficcao = filmes.First(m => m.BatalhasEspaciais == numBatalhas);

Este código funciona, mas é preciso 2 linhas de código em vez de 1, e enumera a coleção várias vezes.

Com os métodos de extensão LINQ MinBy e MaxBy do .NET 6, agora podemos fazer isso mais rapidamente em uma única linha de código:

 
   Filme ficcao = filmes.MaxBy(m => m.BatalhasEspaciais);    

 

A mesma lógica se aplica ao método de extensão MinBy.

Assim, com essas melhorias , codar com LINQ ficou mais fácil e simples.

"Sei estar abatido, e sei também ter abundância; em toda a maneira, e em todas as coisas estou instruído, tanto a ter fartura, como a ter fome; tanto a ter abundância, como a padecer necessidade.
Posso todas as coisas em Cristo que me fortalece."
Filipenses 4:12,13


Referências:


José Carlos Macoratti