转载

linq distinct 不够用了!

问题引出:在实际中遇到一个问题,要进行集合去重,集合内存储的是引用类型,需要根据id进行去重。这个时候linq 的distinct 就不够用了,对于引用类型,它直接比较地址。测试数据如下:

class Person {     public int ID { get; set; }     public string Name { get; set; } } List<Person> list = new List<Person>() {  new Person(){ID=1,Name="name1"},  new Person(){ID=1,Name="name1"},  new Person(){ID=2,Name="name2"},  new Person(){ID=3,Name="name3"}     }; 

我们需要根据Person 的 ID 进行去重。当然使用linq Distinct 不满足,还是有办法实现的,通过GroupBy先分一下组,再取第一个数据即可。例如:

list.GroupBy(x => x.ID).Select(x => x.FirstOrDefault()).ToList()

通常通过GroupBy去实现也是可以的,毕竟在内存操作还是很快的。但这里我们用别的方式去实现,并且找到最好的实现方式。

一、通过IEqualityComparer接口

IEnumerable<T> 的扩展方法 Distinct 定义如下:

public static IEnumerable<TSource> Distinct<TSource>(this IEnumerable<TSource> source); public static IEnumerable<TSource> Distinct<TSource>(this IEnumerable<TSource> source, IEqualityComparer<TSource> comparer);

可以看到,Distinct方法有一个参数为 IEqualityComparer<T> 的重载。该接口的定义如下:

// 类型参数 T: 要比较的对象的类型。 public interface IEqualityComparer<T> {     bool Equals(T x, T y);     int GetHashCode(T obj); }

通过实现这个接口我们就可以实现自己的比较器,定义自己的比较规则了。

这里有一个问题,IEqualityComparer<T> 的 T 是要比较的对象的类型,在这里就是 Person,那这里如何去获得 Person 的属性 id呢?或者说,对于任何类型,我如何知道要比较的是哪个属性?答案就是: 委托 。通过委托,要比较什么属性由外部指定。这也是linq 扩展方法的设计,参数都是委托类型的,也就是规则由外部定义,内部只负责调用。ok,我们看最后实现的代码:

//通过继承EqualityComparer类也是一样的。 class CustomerEqualityComparer<T,V> : IEqualityComparer<T> {  private IEqualityComparer<V> comparer;  private Func<T, V> selector;  public CustomerEqualityComparer(Func<T, V> selector)   :this(selector,EqualityComparer<V>.Default)  {     }  public CustomerEqualityComparer(Func<T, V> selector, IEqualityComparer<V> comparer)  {   this.comparer = comparer;   this.selector = selector;  }  public bool Equals(T x, T y)  {   return this.comparer.Equals(this.selector(x), this.selector(y));  }  public int GetHashCode(T obj)  {   return this.comparer.GetHashCode(this.selector(obj));  } } 

二、通过哈希表。第一种做法的缺点是不仅要定义新的扩展方法,还要定义一个新类。能不能只有一个扩展方法就搞定?可以,通过Dictionary就可以搞定(有HashSet就用HashSet)。实现方式如下:

public static IEnumerable<TSource> Distinct<TSource,TKey>(this IEnumerable<TSource> source, Func<TSource,TKey> selector) {     Dictionary<TKey, TSource> dic = new Dictionary<TKey, TSource>();  foreach (var s in source)  {   TKey key = selector(s);   if (!dic.ContainsKey(key))    dic.Add(key, s);  }  return dic.Select(x => x.Value); } 

三、重写object方法。能不能连扩展方法也不要了?可以。我们知道 object 是所有类型的基类,其中有两个虚方法,Equals、GetHashCode,默认情况下,.net 就是通过这两个方法进行对象间的比较的,那么linq 无参的Distinct 是不是也是根据这两个方法来进行判断的?我们在Person里 override 这两个方法,并实现自己的比较规则。打上断点调试,发现在执行Distinct时,是会进入到这两个方法的。代码如下:

class Person {  public int ID { get; set; }  public string Name { get; set; }  public override bool Equals(object obj)  {   Person p = obj as Person;   return this.ID.Equals(p.ID);  }  public override int GetHashCode()  {   return this.ID.GetHashCode();  } } 

在我的需求里,第三种方式提供了最优雅的实现。

正文到此结束
Loading...