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. 重写Equals
和GetHashCode
方法
可以在类中重写Equals
和GetHashCode
方法,然后使用标准的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
来实现。 - 如果类的去重逻辑是固定的,可以重写
Equals
和GetHashCode
方法,然后使用标准的Distinct()
方法。
常见问题
- 某些查询提供程序无法解析
First
方法:某些查询提供程序可能无法确定每个组至少有一个元素,因此调用First
方法可能会出现问题。在这种情况下,可以使用FirstOrDefault
方法。 - 性能问题:使用
GroupBy
方法进行去重可能会有一定的性能开销,特别是在处理大量数据时。可以考虑使用HashSet
来提高性能。 - 空元素处理:在使用自定义比较逻辑时,需要注意处理集合中可能存在的空元素。可以在比较逻辑中添加空值检查。