Distinct por un campo en C#: cómo eliminar duplicados de objetos

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Problema: Distinct() no sabe filtrar por campo

\n\n

Imagine: tiene una lista de clientes y en ella se han colado duplicados por email. Usted escribe:

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

Y con sorpresa descubre que los duplicados no han desaparecido. ¿Por qué? Porque el Distinct() estándar compara objetos completos por referencia (o por Equals, si lo ha redefinido). No sabe que para usted la "unicidad" es un Email único, no una instancia única de la clase.

\n\n

Esta pregunta es una de las más populares en Stack Overflow sobre LINQ (más de 500 mil visitas). Y hoy analizaremos 3 formas prácticas de resolver este problema en C#. ¡Allá vamos!

\n\n

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

\n\n

El método más simple y claro, que no requiere instalar bibliotecas externas. La idea: agrupar los objetos por la propiedad deseada y luego tomar el primer elemento de cada grupo.

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

Cómo funciona:

\n
    \n
  • GroupBy(c => c.Email) — divide la colección en grupos, donde la clave es el email.
  • \n
  • Select(g => g.First()) — de cada grupo toma el primer objeto que encuentra.
  • \n
  • Los demás duplicados se descartan.
  • \n
\n\n

Ventajas:

\n
    \n
  • Funciona "de serie" (solo using System.Linq;).
  • \n
  • Intuitivo incluso para principiantes.
  • \n
  • Conserva el orden original de los elementos (permanece el primer objeto encontrado).
  • \n
\n\n

Desventajas:

\n
    \n
  • Un poco más lento en grandes volúmenes de datos (necesita crear grupos).
  • \n
  • El código es un poco más verboso de lo deseado.
  • \n
\n\n

Método 2. MoreLinq y DistinctBy (elegancia)

\n\n

Si no le teme a las bibliotecas externas, MoreLinq ofrece el método ideal DistinctBy. Hace exactamente lo que espera de Distinct: filtrar por propiedad.

\n\n

Instalación mediante NuGet:

\n
Install-Package MoreLinq
\n\n

Uso:

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

Una sola línea y todo está listo. Internamente, DistinctBy usa HashSet y funciona en O(n).

\n\n

Ventajas:

\n
    \n
  • Código extremadamente conciso y legible.
  • \n
  • Alto rendimiento.
  • \n
  • Biblioteca bien probada (más de 10 millones de descargas).
  • \n
\n\n

Desventajas:

\n
    \n
  • Requiere agregar una dependencia al proyecto.
  • \n
  • No todos los equipos permiten bibliotecas externas.
  • \n
\n\n

Método 3. IEqualityComparer propio (máximo control)

\n\n

Este método es adecuado cuando no desea agregar una biblioteca, pero quiere reutilizar la lógica de comparación en varios lugares. Creamos nuestro propio 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

Ventajas:

\n
    \n
  • Control total sobre la lógica de comparación.
  • \n
  • El comparador se puede reutilizar en Contains, Except, Union, etc.
  • \n
  • No requiere bibliotecas externas.
  • \n
\n\n

Desventajas:

\n
    \n
  • Mucho código repetitivo.
  • \n

Blogs

Book Recommendations