Question
I am learning LINQ and want to understand how to use Distinct() when working with a list of objects rather than a simple list of values.
For example, suppose I have a Person class with an Id property:
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
}
And I have the following data:
var people = new List<Person>
{
new Person { Id = 1, Name = "Test1" },
new Person { Id = 1, Name = "Test1" },
new Person { Id = 2, Name = "Test2" }
};
How can I return only the unique Person objects based on the Id property, so that the result contains only:
Person1: Id = 1, Name = "Test1"Person3: Id = 2, Name = "Test2"
Is this possible directly with LINQ?
If not, what is the best way to get distinct objects from a List<T> based on one or more of their properties?
Short Answer
By the end of this page, you will understand why Distinct() does not automatically compare object properties, how equality works for custom classes in C#, and the common ways to get unique objects by one or more properties. You will also see practical approaches using GroupBy, custom equality comparers, and DistinctBy in modern C#.
Concept
Distinct() in LINQ removes duplicate items based on equality. For simple types like int, string, or DateTime, this works as you expect because those types already define value-based equality.
For custom classes like Person, the default behavior is different. Unless you define equality yourself, C# compares object references, not property values. That means two different Person instances with the same Id and Name are still considered different objects.
var p1 = new Person { Id = 1, Name = "Test1" };
var p2 = new Person { Id = 1, Name = "Test1" };
Console.WriteLine(p1 == p2); // False
Even though the property values match, these are two separate objects in memory.
So when you call:
people.Distinct()
LINQ only removes duplicates if the objects are considered equal by:
Equals()
Mental Model
Think of Distinct() as a bouncer checking names on a guest list.
- For simple values like numbers, the bouncer can easily tell whether
1is the same as another1. - For objects like
Person, the bouncer does not automatically inspect theIdorNamebadge. - Instead, by default, the bouncer only checks whether it is literally the same person instance entering again.
If you want the bouncer to treat two Person objects with the same Id as duplicates, you must give the bouncer a rule such as:
- “Compare only
Id” - “Compare
IdandName”
That rule can be provided by:
- grouping by a property
- selecting a distinct key
- or writing a custom comparer
Syntax and Examples
1. Using GroupBy()
This is one of the easiest and most readable ways.
var distinctPeople = people
.GroupBy(p => p.Id)
.Select(group => group.First())
.ToList();
How it works
GroupBy(p => p.Id)creates one group for each uniqueIdSelect(group => group.First())picks onePersonfrom each groupToList()turns the result back into a list
For the sample data, the result contains:
Id = 1, Name = "Test1"
Id = 2, Name = "Test2"
2. Using DistinctBy() in modern .NET
If you are using .NET 6 or later, LINQ includes DistinctBy().
var distinctPeople = people
.DistinctBy(p => p.Id)
.ToList();
Step by Step Execution
Consider this example:
var people = new List<Person>
{
new Person { Id = 1, Name = "Test1" },
new Person { Id = 1, Name = "Test1" },
new Person { Id = 2, Name = "Test2" }
};
var distinctPeople = people
.GroupBy(p => p.Id)
.Select(group => group.First())
.ToList();
Step-by-step
Step 1: Start with the original list
[
{ Id = 1, Name = "Test1" },
{ Id = 1, Name = "Test1" },
{ Id = 2, Name = "Test2" }
]
Step 2: GroupBy(p => p.Id)
LINQ groups items by Id.
Group 1:
Key = 1
Items = [
{ Id = 1, Name = "Test1" },
{ Id = 1, Name = "Test1" }
]
Real World Use Cases
Common situations where distinct-by-property is useful
Removing duplicate database results
A query or join may return repeated entities with the same ID.
var uniqueUsers = users.DistinctBy(u => u.Id).ToList();
Importing CSV or API data
A file may contain duplicate rows for the same product.
var uniqueProducts = products.GroupBy(p => p.Sku).Select(g => g.First()).ToList();
Keeping one log entry per request
You may want only one record per request ID.
var uniqueRequests = logs.DistinctBy(log => log.RequestId).ToList();
Merging data from multiple sources
When combining lists, duplicates are common.
var merged = listA.Concat(listB)
.DistinctBy(x => x.Id)
.ToList();
De-duplicating form submissions
If users accidentally submit the same entry twice, you may want to keep just one.
var uniqueEmails = submissions.DistinctBy(s => s.Email).ToList();
Real Codebase Usage
In real projects, developers usually choose one of these patterns depending on where the equality rule belongs.
Query-level uniqueness
If the rule is specific to one query, developers often use GroupBy() or DistinctBy() directly in that query.
var activeUsers = users
.Where(u => u.IsActive)
.DistinctBy(u => u.Id)
.ToList();
This is useful when the uniqueness rule is local and should not affect the entire class.
Reusable comparer objects
If the same distinct logic appears in several places, a custom comparer is common.
var comparer = new PersonIdComparer();
var result = people.Distinct(comparer).ToList();
This avoids repeating the same grouping logic everywhere.
Guarding against null values
In real code, developers often validate input before running LINQ.
if (people == null)
{
return new List<Person>();
}
return people.DistinctBy(p => p.Id).ToList();
Choosing which duplicate to keep
Sometimes duplicates are not identical. Developers then define a rule for which record wins.
Examples:
Common Mistakes
1. Expecting Distinct() to compare properties automatically
Broken example:
var distinctPeople = people.Distinct().ToList();
Why it fails:
- For custom classes, default equality is usually reference-based
- Two separate
Personobjects with the same data are still different
Use one of these instead:
var distinctPeople = people.GroupBy(p => p.Id).Select(g => g.First()).ToList();
or
var distinctPeople = people.DistinctBy(p => p.Id).ToList();
2. Writing Equals() without GetHashCode()
Broken example:
public override bool Equals(object obj)
{
return obj is Person other && Id == other.Id;
}
Why it is a problem:
Comparisons
| Approach | Best for | Returns | Pros | Cons |
|---|---|---|---|---|
Distinct() | Types with built-in equality or custom comparer | Original items | Simple when equality is already defined | Does not work by property automatically |
Distinct(new Comparer()) | Reusable custom equality logic | Original items | Flexible and explicit | Requires extra comparer code |
GroupBy(...).Select(g => g.First()) | Distinct by property in any LINQ version | Original items | Easy to understand, widely available | Slightly more verbose |
DistinctBy(...) | Modern .NET, distinct by key |
Cheat Sheet
// Distinct objects by one property (.NET 6+)
var result = people.DistinctBy(p => p.Id).ToList();
// Distinct objects by multiple properties (.NET 6+)
var result2 = people.DistinctBy(p => new { p.Id, p.Name }).ToList();
// Distinct objects by property (works in older versions)
var result3 = people.GroupBy(p => p.Id)
.Select(g => g.First())
.ToList();
// Distinct property values only
var ids = people.Select(p => p.Id).Distinct().ToList();
// Distinct with custom comparer
var result4 = people.Distinct(new PersonIdComparer()).ToList();
Rules to remember
Distinct()uses equality, not arbitrary property comparison- Custom classes usually need a comparer or overridden equality methods
GroupBy(...).Select(g => g.First())is a common fallbackDistinctBy()is the cleanest option in modern .NET- Use anonymous objects or tuples for multiple properties
Good key choices
- stable IDs
- unique business keys
- normalized text values when needed
Be careful with
- mutable equality fields
- missing
GetHashCode() - null handling in comparers
- assuming inspects object properties automatically
FAQ
Can I use Distinct() directly on a property in C#?
Not directly on the object unless equality is defined for that object. For property-based uniqueness, use DistinctBy(), GroupBy(), or a custom comparer.
What is the easiest way to get distinct objects by one property?
If you are using .NET 6 or later, use:
people.DistinctBy(p => p.Id)
Otherwise, use:
people.GroupBy(p => p.Id).Select(g => g.First())
How do I get distinct objects by multiple properties?
Use a composite key:
people.DistinctBy(p => new { p.Id, p.Name })
or:
people.GroupBy(p => new { p.Id, p.Name }).Select(g => g.First())
Why does Distinct() not remove duplicate objects with the same values?
Because custom class instances are usually compared by reference unless you define value-based equality.
Should I override Equals() and GetHashCode() in my model class?
Mini Project
Description
Build a small C# console example that cleans a list of duplicate people records. This demonstrates how to keep only one Person per Id, which is a common task when importing data, reading API responses, or merging collections.
Goal
Create a program that removes duplicate Person objects based on Id and prints the unique results.
Requirements
- Define a
Personclass withIdandNameproperties. - Create a list containing duplicate
Personrecords. - Produce a unique list based on
Id. - Print the final distinct people to the console.
Keep learning
Related questions
AddTransient vs AddScoped vs AddSingleton in ASP.NET Core Dependency Injection
Learn the differences between AddTransient, AddScoped, and AddSingleton in ASP.NET Core DI with examples and practical usage.
C# Type Checking Explained: typeof vs GetType() vs is
Learn when to use typeof, GetType(), and is in C#. Understand exact type checks, inheritance, and safe type testing clearly.
C# Version Numbers Explained: C# vs .NET Framework and Why “C# 3.5” Is Incorrect
Learn the correct C# version numbers, how they map to .NET releases, and why terms like C# 3.5 are inaccurate and confusing.