LINQ Distinct() по конкретному свойству объекта
Решение через GroupBy и First
Стандартный LINQ не содержит метода DistinctBy (он появится только в .NET 6), но мы можем легко эмулировать эту функциональность с помощью комбинации GroupBy и First. Это самый популярный и читаемый способ.
var uniqueByName = products
.GroupBy(p => p.Name)
.Select(g => g.First())
.ToList();
Как это работает:
GroupBy(p => p.Name)— группирует все продукты по значению свойстваName. В результате получается коллекция групп, где ключ — это имя продукта, а значение — все продукты с таким именем.Select(g => g.First())— из каждой группы берёт первый элемент. Таким образом, для каждого уникального имени остаётся только один продукт.ToList()— материализует результат в список.
Результат для вашего примера: будут выбраны продукты с Id = 1 (Apple), 2 (Banana), 4 (Cherry). Если вы хотите последний дубликат, замените First() на Last().
Универсальный метод-расширение
Если такая операция встречается часто, можно создать собственный метод-расширение:
public static class EnumerableExtensions
{
public static IEnumerable<TSource> DistinctBy<TSource, TKey>(
this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector)
{
var seenKeys = new HashSet<TKey>();
foreach (var element in source)
{
if (seenKeys.Add(keySelector(element)))
{
yield return element;
}
}
}
}
Использование:
var uniqueByName = products.DistinctBy(p => p.Name).ToList();
Этот метод работает эффективнее GroupBy, так как не создаёт промежуточные группы, а использует HashSet для отслеживания уже встреченных ключей. Он сохраняет порядок элементов и возвращает первое вхождение каждого уникального ключа.
Важное замечание
Начиная с .NET 6, в LINQ появился встроенный метод DistinctBy:
var uniqueByName = products.DistinctBy(p => p.Name).ToList();
Если ваш проект использует .NET 6 или новее, это самый простой и производительный вариант.
<<Альтернативный подход: использование MoreLINQ или собственного IEqualityComparer
Если вы не хотите писать свой метод-расширение, можно воспользоваться сторонней библиотекой MoreLINQ (устанавливается через NuGet). Она содержит метод DistinctBy:
using MoreLinq;
var uniqueByName = products.DistinctBy(p => p.Name).ToList();
Это решение полностью аналогично самописному расширению, но не требует его поддержки.
Решение через Distinct с кастомным компаратором
Другой вариант — реализовать IEqualityComparer<Product>:
public class ProductNameComparer : IEqualityComparer<Product>
{
public bool Equals(Product x, Product y) =>
x?.Name == y?.Name;
public int GetHashCode(Product obj) =>
obj.Name?.GetHashCode() ?? 0;
}
// Использование:
var uniqueByName = products.Distinct(new ProductNameComparer()).ToList();
Этот способ менее гибкий (компаратор привязан к конкретному классу и свойству), но может быть полезен, если вы часто фильтруете по Name в разных местах кода.
Рекомендация: Для разовых операций используйте GroupBy + First. Для частого использования — напишите универсальный DistinctBy или подключите MoreLINQ. Избегайте переопределения Equals в самом классе, если это не соответствует его бизнес-логике.