Question

How to Use LINQ Distinct by Property in C#

csharplinqduplicatesuniquedistinct

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()
  • GetHashCode()
  • or a supplied IEqualityComparer<T>

Why this matters

In real applications, you often need uniqueness based on business rules, not full object identity. For example:

  • unique users by Id
  • unique products by Sku
  • unique orders by OrderNumber
  • unique records by a combination like FirstName + LastName + DateOfBirth

That is why knowing how to define equality or choose a LINQ pattern is important.

Main ways to get distinct objects by property

There are three common approaches:

  1. Use GroupBy() and take the first item from each group
  2. Use DistinctBy() if your .NET version supports it
  3. Use Distinct() with a custom comparer

Each option is valid. The best choice depends on your C#/.NET version and whether the equality rule should belong to the class itself or only to one query.

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 1 is the same as another 1.
  • For objects like Person, the bouncer does not automatically inspect the Id or Name badge.
  • 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 Id and Name”

That rule can be provided by:

  • grouping by a property
  • selecting a distinct key
  • or writing a custom comparer
Take Quiz

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 unique Id
  • Select(group => group.First()) picks one Person from each group
  • ToList() 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();

This is often the cleanest solution.

Multiple properties

You can also use more than one property:

var distinctPeople = people
    .DistinctBy(p => new { p.Id, p.Name })
    .ToList();

Or with tuples:

var distinctPeople = people
    .DistinctBy(p => (p.Id, p.Name))
    .ToList();

3. Using Distinct() with a custom comparer

If you want to keep using Distinct(), you can supply an IEqualityComparer<Person>.

public class PersonIdComparer : IEqualityComparer<Person>
{
    public bool Equals(Person x, Person y)
    {
        if (ReferenceEquals(x, y)) return true;
        if (x is null || y is null) return false;
        return x.Id == y.Id;
    }

    public int GetHashCode(Person obj)
    {
        return obj.Id.GetHashCode();
    }
}

Usage:

var distinctPeople = people
    .Distinct(new PersonIdComparer())
    .ToList();

Comparing multiple properties

public class PersonIdNameComparer : IEqualityComparer<Person>
{
    public bool Equals(Person x, Person y)
    {
        if (ReferenceEquals(x, y)) return true;
        if (x is null || y is null) return false;
        return x.Id == y.Id && x.Name == y.Name;
    }

    public int GetHashCode(Person obj)
    {
        return HashCode.Combine(obj.Id, obj.Name);
    }
}

4. Projecting keys only

If you only need the distinct property values, not the full objects:

var distinctIds = people
    .Select(p => p.Id)
    .Distinct()
    .ToList();

This returns only unique Id values, not Person objects.

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" }
]

Group 2:

Key = 2
Items = [
    { Id = 2, Name = "Test2" }
]

Step 3: Select(group => group.First())

From each group, LINQ picks the first object.

Result:

[
    { Id = 1, Name = "Test1" },
    { Id = 2, Name = "Test2" }
]

Step 4: ToList()

The query is converted into a List<Person>.

Important detail

If several objects share the same key, First() keeps the first one encountered. If you need a different rule, you can change it.

For example, choose the person with the longest name:

var distinctPeople = people
    .GroupBy(p => p.Id)
    .Select(group => group.OrderByDescending(p => p.Name.Length).First())
    .ToList();

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();
Take Quiz

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:

  • keep the first record
  • keep the newest record
  • keep the most complete record
  • keep the highest priority record
var latestPeople = people
    .GroupBy(p => p.Id)
    .Select(g => g.OrderByDescending(p => p.LastUpdated).First())
    .ToList();

Validation and normalization

Before applying distinct logic, data may need cleanup.

var uniquePeople = people
    .Where(p => p != null)
    .Where(p => p.Name != null)
    .DistinctBy(p => new { p.Id, NormalizedName = p.Name.Trim().ToLower() })
    .ToList();

This helps avoid treating "Alice" and " alice " as different values when the business rule says they should match.

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 Person objects 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:

  • Hash-based operations like Distinct() depend on both methods
  • If GetHashCode() is inconsistent, results may be wrong or inefficient

Fix:

public override bool Equals(object obj)
{
    return obj is Person other && Id == other.Id;
}

public override int GetHashCode()
{
    return Id.GetHashCode();
}

3. Using mutable properties for equality

If equality depends on a property that changes later, collection behavior can become confusing.

var person = new Person { Id = 1, Name = "A" };

If Id changes after insertion into a hash-based collection, matching may break.

Avoid using frequently changing fields as equality keys.


4. Forgetting null checks in custom comparers

Broken example:

public bool Equals(Person x, Person y)
{
    return x.Id == y.Id;
}

If x or y is null, this throws an exception.

Safer version:

public bool Equals(Person x, Person y)
{
    if (ReferenceEquals(x, y)) return true;
    if (x is null || y is null) return false;
    return x.Id == y.Id;
}

5. Confusing distinct objects with distinct property values

These are not the same:

var ids = people.Select(p => p.Id).Distinct().ToList();

This returns unique IDs.

var persons = people.DistinctBy(p => p.Id).ToList();

This returns Person objects.

Comparisons

ApproachBest forReturnsProsCons
Distinct()Types with built-in equality or custom comparerOriginal itemsSimple when equality is already definedDoes not work by property automatically
Distinct(new Comparer())Reusable custom equality logicOriginal itemsFlexible and explicitRequires extra comparer code
GroupBy(...).Select(g => g.First())Distinct by property in any LINQ versionOriginal itemsEasy to understand, widely availableSlightly more verbose
DistinctBy(...)Modern .NET, distinct by keyOriginal itemsClean and readableRequires .NET 6+
Select(...).Distinct()Distinct scalar values onlyKeys or projected valuesGreat when you only need the property valuesDoes not return the original object

Distinct() vs GroupBy()

people.Distinct(new PersonIdComparer())
  • good when you want a reusable equality rule
  • better if the same logic appears often
people.GroupBy(p => p.Id).Select(g => g.First())
  • good when the rule is only needed once
  • often easier for beginners to read

GroupBy() vs DistinctBy()

people.GroupBy(p => p.Id).Select(g => g.First())
  • works in older LINQ code
  • lets you choose which item to keep from each group
people.DistinctBy(p => p.Id)
  • shorter and clearer
  • ideal when you just want the first item per key
Take Quiz

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 fallback
  • DistinctBy() 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 Distinct() 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?

Only if that equality rule truly represents the object's general identity everywhere in your application. If the rule is query-specific, prefer DistinctBy() or a custom comparer.

Which duplicate does DistinctBy() keep?

It keeps the first item encountered for each key.

Is GroupBy() slower than DistinctBy()?

For most application code, both are fine. Choose the most readable option first unless performance measurements show a real issue.

Take Quiz

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 Person class with Id and Name properties.
  • Create a list containing duplicate Person records.
  • Produce a unique list based on Id.
  • Print the final distinct people to the console.
Take Quiz

Keep learning

Related questions

View more