LINQ - Apresentando os métodos DistinctBy e ExceptBy
 Hoje vou apresentar os novos métodos DistinctBy e ExceptBy da LINQ que operam com conjuntos e estão  disponíveis a partir do .NET 6.

As operações de conjunto na LINQ referem-se a operações de consulta que geram um conjunto de resultados baseado na presença ou ausência de elementos equivalentes dentro da mesma coleção ou de coleções ou conjuntos separados.

Os métodos usados até o momento para realizar estas operações são :

Esses métodos realizam operações em conjuntos em comparação com outros conjuntos de valores.

A partir do NET 6 foram incluídos os seguintes métodos para operações de conjunto :

Eles possuem o mesmo objetivo dos métodos anteriores com a diferença de que operam em objetos complexos e que  eles utilizam uma chave de seleção(KeySelector) usada como um discriminador comparativo para realizar as respectivas operações.

Assim, esses novos métodos utilizam uma propriedade/objeto que deve fazer parte das coleções envolvidas, dispensando assim implementações baseadas na interface IEqualityComparer que eram requeridas por extensões LINQ mais antigas como Union e Intersect.

Neste artigo iremos apresentar um exemplo dos dois primeiros métodos para ilustrar o seu funcionamento.

Usando o método DistinctBy

Vamos criar um projeto Console (.NET Core) no NET 6 chamado LINQ_DistinctBy.

No projeto criado vamos abrir a classe Program e definir um tipo record Aluno:

public record Aluno
{
    public string? Nome { get; init; }
    public byte Idade { get; init; }
}
 

A seguir vamos criar uma lista de alunos :

var alunos = new[]
{
    new Aluno() {Nome = "Maria", Idade = 40},
    new Aluno() {Nome = "Jaime", Idade = 40},
    new Aluno() {Nome = "Jose", Idade = 35},
    new Aluno() {Nome = "Evelyn", Idade = 30},
    new Aluno() {Nome = "Rodrigo", Idade = 36},
    new Aluno() {Nome = "Sonia", Idade = 35},
    new Aluno() {Nome = "Paulo", Idade = 36},
    new Aluno() {Nome = "Carlos", Idade = 33}
};

Para mostrar a atuação do método DistinctBy vamos obter uma lista de alunos com idades únicas ou distintas. Para isso vamos definir a seguinte consulta LINQ

var alunosComIdadesDistintas = alunos.DistinctBy(x => x.Idade);

Aqui usamos o método DistinctBy e definimos a propriedade Idade como a chave de seleção usada como critério para obter a lista de alunos com idades únicas.

Podemos inclusive realizar a ordenação do resultado :

var alunosComIdadesDistintas = alunos.DistinctBy(x => x.Idade).OrderBy(x=> x.Nome);

A seguir podemos percorrer  o resultado obtido, que é um IEnumerable<T>, e exibir os nomes dos alunos:

foreach(var aluno in alunosComIdadesDistintas)
      Console.WriteLine($"Aluno {aluno.Nome} tem {aluno.Idade} anos");

O código completo do projeto é dado a seguir:

var alunos = new[]
{
    new Aluno() {Nome = "Maria", Idade = 40},
    new Aluno() {Nome = "Jaime", Idade = 40},
    new Aluno() {Nome = "Jose", Idade = 35},
    new Aluno() {Nome = "Evelyn", Idade = 30},
    new Aluno() {Nome = "Rodrigo", Idade = 36},
    new Aluno() {Nome = "Sonia", Idade = 35},
    new Aluno() {Nome = "Paulo", Idade = 36},
    new Aluno() {Nome = "Carlos", Idade = 33}
};

Console.WriteLine("\n### Usando DistinctBy ###\n");

var alunosComIdadesDistintas = alunos.DistinctBy(x => x.Idade).OrderBy(y=> y.Idade);

foreach(var aluno in alunosComIdadesDistintas)
    Console.WriteLine($"Aluno {aluno.Nome} tem {aluno.Idade} anos");

Console.ReadKey();

public record Aluno
{
    public string? Nome { get; init; }
    public byte Idade { get; init; }
}

Obs: Aqui usamos o recurso das instruções de nível superior

Executando o projeto teremos o resultado a abaixo:

Usando o método ExceptBy

Vamos criar outro projeto Console (.NET Core) no NET 6 chamado LINQ_ExceptBy.

No projeto criado vamos abrir a classe Program e definir um tipo record Planeta:

public record Planeta
{
    public string? Nome { get; init; }
    public string? Tipo { get; init; }
}

A seguir vamos criar uma lista de planetas contendo todos os planetas do sistema solar:

var planetas = new []
{
  new Planeta{Nome="Mercurio",Tipo = "Rochoso"},
  new Planeta{Nome="Vênus", Tipo="Rochoso"},
  new Planeta{Nome="Marte", Tipo="Rochoso"},
  new Planeta{Nome="Terra", Tipo="Rochoso"},
  new Planeta{Nome="Júpiter", Tipo="Gasoso"},
  new Planeta{Nome="Saturno", Tipo="Gasoso"},
  new Planeta{Nome="Urano", Tipo="Gasoso"},
  new Planeta{Nome="Netuno", Tipo="Gasoso"},
  new Planeta{Nome="Plutão", Tipo="Rochoso"}
};

Vamos criar também outra lista de planetas gasosos (jovianos) :

var jovianos = new[]
{
  new Planeta{Nome="Júpiter", Tipo="Gasoso"},
  new Planeta{Nome="Saturno", Tipo="Gasoso"},
  new Planeta{Nome="Urano", Tipo="Gasoso"},
  new Planeta{Nome="Netuno", Tipo="Gasoso"}
};

Feito isso, o nosso objetivo será obter uma lista dos planetas com exceção dos planetas jovianos. Para isso usamos o método Exceptby onde :

1 - Vamos definir a coleção que contem os elementos que desejamos retirar da primeira coleção , ou seja da coleção planetas, e, aplicar o método ExceptBy;
2 - A seguir vamos definir a chave de seleção que é a propriedade Nome;

var planetasExcetoJovianos = planetas.ExceptBy(jovianos.Select(p => p.Nome), p => p.Nome).OrderBy(x=> x.Nome);

Com isso vamos obter todos os planetas, exceto aqueles planetas cujo nome identificam um planeta joviano ou gasoso,
e, ainda estamos ordenando o resultado.

O código completo do projeto é dado a seguir:

var planetas = new []
{
  new Planeta{Nome="Mercurio",Tipo = "Rochoso"},
  new Planeta{Nome="Vênus", Tipo="Rochoso"},
  new Planeta{Nome="Marte", Tipo="Rochoso"},
  new Planeta{Nome="Terra", Tipo="Rochoso"},
  new Planeta{Nome="Júpiter", Tipo="Gasoso"},
  new Planeta{Nome="Saturno", Tipo="Gasoso"},
  new Planeta{Nome="Urano", Tipo="Gasoso"},
  new Planeta{Nome="Netuno", Tipo="Gasoso"},
  new Planeta{Nome="Plutão", Tipo="Rochoso"}
};

var jovianos = new[]
{ new Planeta{Nome="Júpiter", Tipo="Gasoso"},
  new Planeta{Nome="Saturno", Tipo="Gasoso"},
  new Planeta{Nome="Urano", Tipo="Gasoso"},
  new Planeta{Nome="Netuno", Tipo="Gasoso"}
};

Console.WriteLine("\n### Usando ExceptBy ###\n");

var planetasExcetoJovianos =
planetas.ExceptBy(jovianos.Select(p => p.Nome), p => p.Nome)
                                         .OrderBy(x=> x.Nome);

foreach(var planeta in planetasExcetoJovianos)
    Console.WriteLine($"\t{planeta.Nome} - {planeta.Tipo}");

Console.ReadKey();

public record Planeta
{
    public string? Nome { get; init; }
    public string? Tipo { get; init; }
}

Obs: Aqui usamos o recurso das instruções de nível superior

Executando o projeto teremos o seguinte resultado:

Com o uso destes métodos temos um código mais sucinto e legível e que realiza a tarefa proposta em apenas uma etapa.

Na próxima parte do artigo veremos os métodos : IntercepBy e UnionBy

Pegue o código dos dois projetos aqui : linq1.zip

"Deus "retribuirá a cada um conforme o seu procedimento".
Ele dará vida eterna aos que, persistindo em fazer o bem, buscam glória, honra e imortalidade."
Romanos 2:6,7

E estamos conversados.

Referências:


José Carlos Macoratti