Distinct por um campo em C#: como remover duplicatas de objetos

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

Problema: Distinct() não sabe filtrar por campo

\n\n

Imagine: você tem uma lista de clientes e nela existem duplicatas por email. Você escreve:

\n\n
var uniqueClients = clients.Distinct().ToList();
\n\n

E descobre, surpreso, que as duplicatas não desapareceram. Por quê? Porque o Distinct() padrão compara objetos inteiros por referência (ou por Equals, se você o sobrescreveu). Ele não sabe que para você "unicidade" significa um Email único, e não uma instância única da classe.

\n\n

Esta pergunta é uma das mais populares no Stack Overflow sobre LINQ (mais de 500 mil visualizações). E hoje vamos analisar 3 formas práticas de resolver este problema em C#. Vamos lá!

\n\n

Método 1. GroupBy + First (clássico)

\n\n

O método mais simples e compreensível, que não requer instalação de bibliotecas externas. A ideia: agrupar os objetos pela propriedade desejada e, em seguida, pegar o primeiro elemento de cada grupo.

\n\n
var uniqueByEmail = clients\n    .GroupBy(c => c.Email)\n    .Select(g => g.First())\n    .ToList();
\n\n

Como funciona:

\n
    \n
  • GroupBy(c => c.Email) — divide a coleção em grupos, onde a chave é o email.
  • \n
  • Select(g => g.First()) — de cada grupo, pega o primeiro objeto encontrado.
  • \n
  • As demais duplicatas são descartadas.
  • \n
\n\n

Vantagens:

\n
    \n
  • Funciona "de fábrica" (apenas using System.Linq;).
  • \n
  • Intuitivo até para iniciantes.
  • \n
  • Preserva a ordem original dos elementos (o primeiro objeto encontrado permanece).
  • \n
\n\n

Desvantagens:

\n
    \n
  • Um pouco mais lento em grandes volumes de dados (precisa criar grupos).
  • \n
  • Código um pouco mais verboso do que o desejado.
  • \n
\n\n

Método 2. MoreLinq e DistinctBy (elegância)

\n\n

Se você não tem receio de bibliotecas externas, o MoreLinq oferece o método ideal DistinctBy. Ele faz exatamente o que você espera do Distinct — filtra por propriedade.

\n\n

Instalação via NuGet:

\n
Install-Package MoreLinq
\n\n

Uso:

\n
using MoreLinq;\n\nvar uniqueByEmail = clients.DistinctBy(c => c.Email).ToList();
\n\n

Uma linha e está pronto. Internamente, o DistinctBy usa HashSet e opera em O(n).

\n\n

Vantagens:

\n
    \n
  • Código extremamente conciso e legível.
  • \n
  • Alta performance.
  • \n
  • Biblioteca bem testada (mais de 10 milhões de downloads).
  • \n
\n\n

Desvantagens:

\n
    \n
  • É necessário adicionar uma dependência ao projeto.
  • \n
  • Nem todas as equipes permitem bibliotecas externas.
  • \n
\n\n

Método 3. IEqualityComparer personalizado (controle máximo)

\n\n

Este método é adequado quando você não quer adicionar uma biblioteca, mas deseja reutilizar a lógica de comparação em vários lugares. Criamos nosso próprio comparador:

\n\n
public class ClientEmailComparer : IEqualityComparer<Client>\n{\n    public bool Equals(Client x, Client y)\n    {\n        if (ReferenceEquals(x, y)) return true;\n        if (x is null || y is null) return false;\n        return x.Email == y.Email;\n    }\n\n    public int GetHashCode(Client obj)\n    {\n        return obj.Email?.GetHashCode() ?? 0;\n    }\n}
\n\n

Uso:

\n
var comparer = new ClientEmailComparer();\nvar uniqueByEmail = clients.Distinct(comparer).ToList();
\n\n

Vantagens:

\n
    \n
  • Controle total sobre a lógica de comparação.
  • \n
  • O comparador pode ser reutilizado em Contains, Except, Union, etc.
  • \n
  • Não requer bibliotecas externas.
  • \n
\n\n

Desvantagens:

\n
    \n
  • Muito código boilerplate.
  • \n

Blogs

Book Recommendations