LINQ's Distinct() on a particular property

LINQ’s Distinct() on a particular property

技术背景

在使用LINQ进行数据查询时,有时需要根据一个或多个属性来获取不同元素的列表。然而,标准的Distinct()方法是基于对象的整体相等性来判断的,无法直接根据特定属性进行去重。因此,需要采用一些技巧或扩展方法来实现基于特定属性的去重功能。

实现步骤

1. 使用GroupBy方法

可以使用GroupBy方法对数据进行分组,然后从每个组中选择一个元素,从而实现去重。

1
2
3
4
List<Person> distinctPeople = allPeople
.GroupBy(p => p.PersonId)
.Select(g => g.First())
.ToList();

如果要根据多个属性进行分组,可以使用匿名类型:

1
2
3
4
List<Person> distinctPeople = allPeople
.GroupBy(p => new {p.PersonId, p.FavoriteColor} )
.Select(g => g.First())
.ToList();

2. 自定义DistinctBy扩展方法

可以自定义一个DistinctBy扩展方法,该方法使用HashSet来跟踪已经出现过的键,从而实现去重。

1
2
3
4
5
6
7
8
9
10
11
12
public static IEnumerable<TSource> DistinctBy<TSource, TKey>
(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
{
HashSet<TKey> seenKeys = new HashSet<TKey>();
foreach (TSource element in source)
{
if (seenKeys.Add(keySelector(element)))
{
yield return element;
}
}
}

使用示例:

1
2
var query = people.DistinctBy(p => p.Id);
var query = people.DistinctBy(p => new { p.Id, p.Name });

3. 使用.NET 6及以上版本的DistinctBy方法

从.NET 6开始,LINQ提供了DistinctBy扩展方法,可以直接使用:

1
var distinctPersonsById = personList.DistinctBy(x => x.Id);

4. 使用IEqualityComparer

可以创建一个自定义的IEqualityComparer来实现基于特定属性的去重:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class CustomEqualityComparer<T> : IEqualityComparer<T>
{
Func<T, T, bool> _comparison;
Func<T, int> _hashCodeFactory;

public CustomEqualityComparer(Func<T, T, bool> comparison, Func<T, int> hashCodeFactory)
{
_comparison = comparison;
_hashCodeFactory = hashCodeFactory;
}

public bool Equals(T x, T y)
{
return _comparison(x, y);
}

public int GetHashCode(T obj)
{
return _hashCodeFactory(obj);
}
}

Func<Person, Person, bool> areEqual = (p1, p2) => int.Equals(p1.Id, p2.Id);
Func<Person, int> getHashCode = (p) => p.Id.GetHashCode();
var query = people.Distinct(new CustomEqualityComparer<Person>(areEqual, getHashCode));

5. 重写EqualsGetHashCode方法

可以在类中重写EqualsGetHashCode方法,然后使用标准的Distinct()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Person
{
public int Id { get; set; }
public int Name { get; set; }

public override bool Equals(object obj)
{
return ((Person)obj).Id == Id;
}
public override int GetHashCode()
{
return Id.GetHashCode();
}
}

List<Person> distinctList = new[] { person1, person2, person3 }.Distinct().ToList();

核心代码

以下是自定义DistinctBy扩展方法的核心代码:

1
2
3
4
5
6
7
8
9
10
11
12
public static IEnumerable<TSource> DistinctBy<TSource, TKey>
(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
{
HashSet<TKey> seenKeys = new HashSet<TKey>();
foreach (TSource element in source)
{
if (seenKeys.Add(keySelector(element)))
{
yield return element;
}
}
}

最佳实践

  • 如果使用.NET 6及以上版本,优先使用内置的DistinctBy方法,因为它更简洁且性能可能更好。
  • 如果需要兼容旧版本的.NET,可以自定义DistinctBy扩展方法或使用GroupBy方法。
  • 对于复杂的比较逻辑,可以使用IEqualityComparer来实现。
  • 如果类的去重逻辑是固定的,可以重写EqualsGetHashCode方法,然后使用标准的Distinct()方法。

常见问题

  • 某些查询提供程序无法解析First方法:某些查询提供程序可能无法确定每个组至少有一个元素,因此调用First方法可能会出现问题。在这种情况下,可以使用FirstOrDefault方法。
  • 性能问题:使用GroupBy方法进行去重可能会有一定的性能开销,特别是在处理大量数据时。可以考虑使用HashSet来提高性能。
  • 空元素处理:在使用自定义比较逻辑时,需要注意处理集合中可能存在的空元素。可以在比较逻辑中添加空值检查。

LINQ's Distinct() on a particular property
https://119291.xyz/posts/linq-distinct-on-a-particular-property/
作者
ww
发布于
2025年6月6日
许可协议