博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
linq distinct 不够用了!
阅读量:4653 次
发布时间:2019-06-09

本文共 4082 字,大约阅读时间需要 13 分钟。

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

class Person    {        public int ID { get; set; }        public string Name { get; set; }    }    List
list = new List
() { 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
Distinct
(this IEnumerable
source);public static IEnumerable
Distinct
(this IEnumerable
source, IEqualityComparer
comparer);

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

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

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

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

//通过继承EqualityComparer类也是一样的。    class CustomerEqualityComparer
: IEqualityComparer
{ private IEqualityComparer
comparer; private Func
selector; public CustomerEqualityComparer(Func
selector) :this(selector,EqualityComparer
.Default) { } public CustomerEqualityComparer(Func
selector, IEqualityComparer
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)); } }

 

(补充1)之前没有把扩展方法贴出来,而且看到有朋友提到比较字符串忽略大小写的问题(其实上面有两个构造函数就可以解决这个问题)。这里扩展方法可以写为:

static class EnumerableExtention    {        public static IEnumerable
Distinct
(this IEnumerable
source, Func
selector) { return source.Distinct(new CustomerEqualityComparer
(selector)); } //4.0以上最后一个参数可以写成默认参数 EqualityComparer
.Default,两个扩展Distinct可以合并为一个。 public static IEnumerable
Distinct
(this IEnumerable
source, Func
selector, IEqualityComparer
comparer) { return source.Distinct(new CustomerEqualityComparer
(selector,comparer)); } }

例如,要根据Person的Name忽略大小写比较,就可以写成:

list.Distinct(x => x.Name,StringComparer.CurrentCultureIgnoreCase).ToList(); //StringComparer实现了IEqualityComaparer
接口

 

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

public static IEnumerable
Distinct
(this IEnumerable
source, Func
selector) { Dictionary
dic = new Dictionary
(); 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();    }}

在我的需求里,是根据id去重的,所以第三种方式提供了最优雅的实现。如果是其它情况,用前面的方法更通用。

转载于:https://www.cnblogs.com/4littleProgrammer/p/4759356.html

你可能感兴趣的文章
shell脚本练习01
查看>>
WPF图标拾取器
查看>>
通过取父级for循环的i来理解闭包,iife,匿名函数
查看>>
HDU 3374 String Problem
查看>>
数据集
查看>>
[Leetcode] unique paths ii 独特路径
查看>>
HDU 1217 Arbitrage (Floyd + SPFA判环)
查看>>
IntelliJ idea学习资源
查看>>
Django Rest Framework -解析器
查看>>
ExtJs 分组表格控件----监听
查看>>
Hibernate二级缓存配置
查看>>
LoadRunner常用术语
查看>>
关于jedis2.4以上版本的连接池配置,及工具类
查看>>
记忆讲师石伟华微信公众号2017所有文章汇总(待更新)
查看>>
mechanize (1)
查看>>
FactoryBean
查看>>
Coolite动态加载CheckboxGroup,无法在后台中获取
查看>>
如何在我们项目中利用开源的图表(js chart)
查看>>
nfs服务器工作原理
查看>>
C3P0连接池工具类使用
查看>>