Problem: Distinct() Cannot Filter by Field
\n\nImagine: you have a list of clients, and there are duplicates by email. You write:
\n\nvar uniqueClients = clients.Distinct().ToList();\n\nAnd you are surprised to find that the duplicates are still there. Why? Because the standard Distinct() compares entire objects by reference (or by Equals, if you have overridden it). It doesn't know that for you, "uniqueness" means a unique Email, not a unique class instance.
This question is one of the most popular on Stack Overflow regarding LINQ (over 500 thousand views). And today we will break down 3 working methods to solve this problem in C#. Let's go!
\n\nMethod 1. GroupBy + First (the classic)
\n\nThe simplest and most understandable method that does not require installing third-party libraries. The idea: group objects by the desired property, then take the first element from each group.
\n\nvar uniqueByEmail = clients\n .GroupBy(c => c.Email)\n .Select(g => g.First())\n .ToList();\n\nHow it works:
\n- \n
GroupBy(c => c.Email)— splits the collection into groups where the key is the email. \n Select(g => g.First())— takes the first object from each group. \n - The remaining duplicates are discarded. \n
Pros:
\n- \n
- Works "out of the box" (only
using System.Linq;). \n - Intuitively understandable even for a beginner. \n
- Preserves the original order of elements (the first encountered object remains). \n
Cons:
\n- \n
- Slightly slower on large data (needs to create groups). \n
- The code is a bit more verbose than desired. \n
Method 2. MoreLinq and DistinctBy (elegance)
\n\nIf you are not afraid of third-party libraries, then MoreLinq offers the ideal DistinctBy method. It does exactly what you expect from Distinct — filters by property.
Installation via NuGet:
\nInstall-Package MoreLinq\n\nUsage:
\nusing MoreLinq;\n\nvar uniqueByEmail = clients.DistinctBy(c => c.Email).ToList();\n\nOne line — and it's done. Internally, DistinctBy uses a HashSet and works in O(n).
Pros:
\n- \n
- Extremely concise and readable code. \n
- High performance. \n
- The library is well-tested (over 10 million downloads). \n
Cons:
\n- \n
- Need to add a dependency to the project. \n
- Not all teams allow third-party libraries. \n
Method 3. Custom IEqualityComparer (maximum control)
\n\nThis method is suitable when you don't want to pull in a library, but you want to reuse the comparison logic in multiple places. Create your own comparer:
\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\nUsage:
\nvar comparer = new ClientEmailComparer();\nvar uniqueByEmail = clients.Distinct(comparer).ToList();\n\nPros:
\n- \n
- Full control over the comparison logic. \n
- The comparer can be reused in
Contains,Except,Union, etc. \n - No external libraries required. \n
Cons:
\n- \n
- Lot of boilerplate code. \n