Problème : Distinct() ne sait pas filtrer par champ
\n\nImaginez : vous avez une liste de clients, et des doublons par email se sont glissés dedans. Vous écrivez :
\n\nvar uniqueClients = clients.Distinct().ToList();\n\nEt vous découvrez avec surprise que les doublons n'ont pas disparu. Pourquoi ? Parce que le Distinct() standard compare les objets entiers par référence (ou par Equals, si vous l'avez redéfini). Il ne sait pas que pour vous, « l'unicité » signifie un Email unique, et non une instance de classe unique.
Cette question est l'une des plus populaires sur Stack Overflow concernant LINQ (plus de 500 000 vues). Et aujourd'hui, nous allons voir 3 méthodes efficaces pour résoudre ce problème en C#. C'est parti !
\n\nMéthode 1. GroupBy + First (le classique)
\n\nLa méthode la plus simple et la plus compréhensible, qui ne nécessite pas l'installation de bibliothèques externes. L'idée : regrouper les objets par la propriété souhaitée, puis prendre le premier élément de chaque groupe.
\n\nvar uniqueByEmail = clients\n .GroupBy(c => c.Email)\n .Select(g => g.First())\n .ToList();\n\nComment cela fonctionne :
\n- \n
GroupBy(c => c.Email)— divise la collection en groupes dont la clé est l'email. \n Select(g => g.First())— prend le premier objet de chaque groupe. \n - Les autres doublons sont écartés. \n
Avantages :
\n- \n
- Fonctionne « prêt à l'emploi » (uniquement
using System.Linq;). \n - Intuitif même pour un débutant. \n
- Conserve l'ordre original des éléments (le premier objet rencontré reste). \n
Inconvénients :
\n- \n
- Légèrement plus lent sur de grandes données (nécessite la création de groupes). \n
- Code un peu plus verbeux que souhaité. \n
Méthode 2. MoreLinq et DistinctBy (élégance)
\n\nSi vous n'avez pas peur des bibliothèques externes, MoreLinq propose la méthode idéale DistinctBy. Elle fait exactement ce que vous attendez de Distinct — filtrer par propriété.
Installation via NuGet :
\nInstall-Package MoreLinq\n\nUtilisation :
\nusing MoreLinq;\n\nvar uniqueByEmail = clients.DistinctBy(c => c.Email).ToList();\n\nUne seule ligne et tout est prêt. En interne, DistinctBy utilise un HashSet et fonctionne en O(n).
Avantages :
\n- \n
- Code extrêmement concis et lisible. \n
- Haute performance. \n
- Bibliothèque bien testée (plus de 10 millions de téléchargements). \n
Inconvénients :
\n- \n
- Nécessite d'ajouter une dépendance au projet. \n
- Toutes les équipes n'autorisent pas les bibliothèques externes. \n
Méthode 3. IEqualityComparer personnalisé (contrôle maximal)
\n\nCette méthode convient lorsque vous ne voulez pas ajouter de bibliothèque, mais que vous souhaitez réutiliser la logique de comparaison à plusieurs endroits. Créez votre propre comparateur :
\n\npublic 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\nUtilisation :
\nvar comparer = new ClientEmailComparer();\nvar uniqueByEmail = clients.Distinct(comparer).ToList();\n\nAvantages :
\n- \n
- Contrôle total sur la logique de comparaison. \n
- Le comparateur peut être réutilisé dans
Contains,Except,Union, etc. \n - Ne nécessite pas de bibliothèques externes. \n
Inconvénients :
\n- \n
- Beaucoup de code passe-partout. \n