Métodos LINQ avançados: Distinct, Except, Intersect e Union

Neste artigo abordaremos quatro métodos muito importantes do LINQ (Language Integrated Query): Distinct, Except, Intersect e Union. Além disso, exploraremos suas versões aprimoradas (com o sufixo “By”), que permitem a definição de uma chave específica para comparação. Vamos examinar como esses métodos funcionam e em que situações as variações com o “By” são mais úteis, com exemplos práticos em C#.

Distinct e DistinctBy

O método “Distinct” é utilizado para remover duplicatas de uma coleção, retornando apenas os elementos únicos. Ele é útil quando queremos evitar valores repetidos.

Suponha que temos uma lista de números e queremos obter apenas os valores únicos:

				
					List<int> numeros = new List<int> { 1, 2, 3, 2, 4, 4, 5 };
var numerosUnicos = numeros.Distinct().ToList();
Console.WriteLine(string.Join(", ", numerosUnicos)); 

				
			

O resultado será uma lista sem repetições de elementos, ou seja, “1, 2, 3, 4, 5”.

Já o “DistinctBy” é uma versão mais flexível que permite definir uma chave específica para determinar a unicidade dos elementos. Assim, ele é útil para comparar apenas um campo ou propriedade, mantendo elementos únicos com base na chave escolhida.

Por exemplo, considere uma lista de objetos “Pessoa”, em que queremos apenas uma instância de cada pessoa com base na” Cidade”.

				
					List<Pessoa> pessoas = new List<Pessoa>
{
    new Pessoa { Nome = "João", Cidade = "São Paulo" },
    new Pessoa { Nome = "Maria", Cidade = "Rio de Janeiro" },
    new Pessoa { Nome = "Pedro", Cidade = "São Paulo" }
};

var pessoasDistintasPorCidade = pessoas.DistinctBy(p => p.Cidade).ToList();
foreach (var pessoa in pessoasDistintasPorCidade)
{
    Console.WriteLine($"{pessoa.Nome} - {pessoa.Cidade}"); 
}

				
			

O resultado será: “João – São Paulo, Maria – Rio de Janeiro”. Aqui, o “DistinctBy” elimina duplicatas com base no campo “Cidade”, retornando uma lista onde cada cidade aparece apenas uma vez.

Except e ExceptBy

O método “Except” retorna os elementos de uma coleção que não estão presentes em outra, funcionando como uma operação de diferença. Ele é útil para excluir elementos comuns entre duas listas.

Considere duas listas de números. Queremos saber quais números estão na primeira lista, mas não na segunda.

				
					List<int> lista1 = new List<int> { 1, 2, 3, 4, 5 };
List<int> lista2 = new List<int> { 3, 4, 5, 6, 7 };

var diferenca = lista1.Except(lista2).ToList();
Console.WriteLine(string.Join(", ", diferenca));

				
			

O resultado final será uma lista com os elementos 1 e 2, pois eles são os únicos elementos que não estão na “lista2”.

Já o “ExceptBy” permite que você defina uma chave específica para realizar a comparação, sendo especialmente útil quando queremos comparar apenas um campo entre duas coleções.

Agora, suponha duas listas de objetos “Produto”. Queremos saber quais produtos na primeira lista não estão na segunda, mas comparando apenas pelo campo “Codigo”.

				
					List<Produto> produtosLoja1 = new List<Produto>
{
    new Produto { Codigo = 1, Nome = "Camiseta" },
    new Produto { Codigo = 2, Nome = "Calça" },
    new Produto { Codigo = 3, Nome = "Tênis" }
};

List<Produto> produtosLoja2 = new List<Produto>
{
    new Produto { Codigo = 3, Nome = "Tênis" },
    new Produto { Codigo = 4, Nome = "Boné" }
};

var produtosExclusivosLoja1 = produtosLoja1.ExceptBy(produtosLoja2.Select(p => p.Codigo), p => p.Codigo).ToList();
foreach (var produto in produtosExclusivosLoja1)
{
    Console.WriteLine($"{produto.Nome} - {produto.Codigo}"); // Saída: Camiseta - 1, Calça - 2
}

				
			

O resultado será a lista “produtosExclusivosLoja1” contendo apenas os itens camiseta e calça.

Intersect e IntersectBy

O método “Intersect” vai realizar a ação inversa ao método “Except”. Ele vai retornar apenas os elementos que estão presentes em ambas as coleções, funcionando como uma operação de interseção. É útil quando queremos saber quais itens duas coleções compartilham.

Vamos supor que temos duas listas de números e queremos saber quais números estão presentes em ambas.

				
					List<int> numeros1 = new List<int> { 1, 2, 3, 4 };
List<int> numeros2 = new List<int> { 3, 4, 5, 6 };

var intersecao = numeros1.Intersect(numeros2).ToList();
Console.WriteLine(string.Join(", ", intersecao));

				
			

Com isso, o nosso resultado será armazenado na lista “intersecao ” contendo apenas os valores 3 e 4.

Já o “IntersectBy” permite fazer essa interseção que seja baseada em uma chave específica, que é ideal para cenários onde queremos comparar apenas um campo ou propriedade entre duas listas.

Agora, considere duas listas de objetos “Aluno”. Queremos saber quais alunos estão matriculados em ambas as turmas, comparando apenas pelo “Id”.

				
					List<Aluno> turmaA = new List<Aluno>
{
    new Aluno { Id = 1, Nome = "Carlos" },
    new Aluno { Id = 2, Nome = "Ana" },
};

List<Aluno> turmaB = new List<Aluno>
{
    new Aluno { Id = 2, Nome = "Ana" },
    new Aluno { Id = 3, Nome = "Mariana" }
};

var alunosEmAmbasTurmas = turmaA.IntersectBy(turmaB.Select(a => a.Id), a => a.Id).ToList();
foreach (var aluno in alunosEmAmbasTurmas)
{
    Console.WriteLine($"{aluno.Nome} - {aluno.Id}");
}

				
			

Neste exemplo, o “IntersectBy” retorna apenas a aluna “Ana” que está em ambas as turmas.

Union e UnionBy

Por fim, o método “Union” combina duas coleções, retornando uma única lista que contém todos os elementos, sem duplicatas. Ele é útil para unir listas de mesmo tipo enquanto remove repetições.

Suponha que temos duas listas de números e queremos combiná-las, mas mantendo apenas valores únicos.

				
					List<int> numerosA = new List<int> { 1, 2, 3 };
List<int> numerosB = new List<int> { 3, 4, 5 };

var uniao = numerosA.Union(numerosB).ToList();
Console.WriteLine(string.Join(", ", uniao));

				
			

O código acima nos retornará uma lista com os elementos 1,2,3,4,5.

A sua variação, o “UnionBy”, permite definir uma chave específica para determinar a unicidade dos elementos combinados, sendo prático para garantir que objetos com valores duplicados em uma propriedade específica sejam removidos.

Agora, imagine duas listas de objetos “Carro” onde queremos combinar as listas e manter apenas uma instância de cada carro com base na “Placa”.

				
					List<Carro> carrosGaragem1 = new List<Carro>
{
    new Carro { Placa = "ABC1234", Modelo = "Sedan" },
    new Carro { Placa = "DEF5678", Modelo = "SUV" }
};

List<Carro> carrosGaragem2 = new List<Carro>
{
    new Carro { Placa = "DEF5678", Modelo = "SUV" },
    new Carro { Placa = "GHI9101", Modelo = "Hatch" }
};

var todosCarros = carrosGaragem1.UnionBy(carrosGaragem2, c => c.Placa).ToList();
foreach (var carro in todosCarros)
{
    Console.WriteLine($"{carro.Modelo} - {carro.Placa}"); 
}

				
			

Com “UnionBy”, a lista resultante contém apenas uma instância de cada “Placa”, unindo os carros das duas garagens, ou seja, os carros “Sedan – ABC1234”, “SUV – DEF5678” e “Hatch – GHI9101”.

Acelere a sua carreira conosco!

Se você é Desenvolvedor .NET Júnior e quer acelerar sua carreira até nível Pleno com salário de R$7k+, ou mesmo busca a primeira vaga, conheça a Mentoria .NET StartClique aqui
Se é Desenvolvedor .NET Pleno ou Sênior e quer virar referência técnica em sua equipe e mercado, com salário de R$10k+, conheça a Mentoria .NET ExpertClique aqui

Conclusão

Os métodos “Distinct”, “Except”, “Intersect” e “Union” do LINQ, juntamente com suas variações “By”, são ferramentas essenciais para manipular coleções de dados no .NET. A escolha de utilizar a versão com “By” depende da necessidade de uma chave de comparação específica, que proporciona maior flexibilidade ao lidar com objetos complexos. Com esses métodos, você pode simplificar consultas e operações com dados de maneira clara e eficiente em seus projetos .NET.