利用Distinct()内置方法对List集合的去重问题详解

前言

说到对集合去重处理,第一时间想到的肯定是Linq的Distinct扩展方式,对于一般的值类型集合去重,很好处理,直接list.Distinct()即可。但是如果想要对一个引用类型的集合去重(属性值都相同就认为重复),就会发现,直接Distinct()是不行的

先来看看泛型链表 List<T> 的定义:

public class List<T> : IList<T>, ICollection<T>, IList, ICollection, IReadOnlyList<T>, IReadOnlyCollection<T>, IEnumerable<T>, IEnumerable

可见它实现了 IEnumerable<T>,而IEnumerable<T>规定了Distinct方法。

使用这个方法时要注意:

(1)该方法并不会改变原来的链表;

(2)该方法返回一个对象(假设叫做dis),通过该对象可以枚举原链表中的非重复元素,但是并没有把非重复元素复制一份到新的对象中(连签拷贝也没有)

(3)由于(2),在枚举dis时,始终是依赖于原有链表,所以如果在获得dis后,又更新了原有链表,那么使用dis枚举将会使用原有链表的最新状态。

 var list=new List<SampleVersionDto>()///表明具有重复值得集合

有时候Distinct()不能对引用类型去重时 我们就要自定义了 自定义代码如下:

public class User
{
 public int Id { get; set; }
 public string Name { get; set; }
}

var list = new List<User>()
{
 new User() { Id = 1, Name = "张三" } ,
 new User() { Id = 1, Name = "张三" } ,
 new User() { Id = 3, Name = "李四" } ,
};

var newList1 = list.Distinct().ToList();

运行上述代码会发现,并不是预期想要的结果,newList1还是有3个元素。之所以会产生这样的结果,是因为Distinct()是通过使用默认的相等比较器对值进行比较返回序列中的非重复元素。对于值类型,默认的相等比较器是比较值是否相等,对于引用类型,默认的相等比较器是比较对象的引用地址,所以上述例子中即使属性值都相同,也不能去重。

IEqualityComparer<TSource>

聪明的我们,很容易就能发现,Linq已经为我们重载了一个去重方法,可以满足我们的需求:

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

重载的这个方法,多提供了一个参数IEqualityComparer<TSource> comparer,是一个泛型接口,我们只需要对这个接口进行实现,即可满足我们的去重需求:

public class UserComparer : IEqualityComparer<User>
{
 public bool Equals(User x, User y)
 {
 return x.Id == y.Id && x.Name == y.Name;
 }

 public int GetHashCode(User obj)
 {
 return obj.ToString().GetHashCode();
 }
}

IEqualityComparer<TSource> 定义了两个方法,一个是Equals,一个是GetHashCode。这里我查找参考资料发现,进行比较时,默认先通过GetHashCode对两个元素进行比较,如果HashCode不同,则认为两个元素不同,如果相同则再通过Equals方法比较。所以这里我不能直接将User对象GetHashCode处理,而是先转换成了字符串再GetHashCode。通过这个重载方法,我们就可以到达目的了:

ar newList2 = list.Distinct(new UserComparer()).ToList();

甚至我们还可以实现只要某个属性相同就认为重复的效果,只需要在Equals方法按想要比较方式进行处理即可

延伸思考

Distinct的重载方法,基本已经能够满足我们的各式各样的去重需求了,但是想来想去,还是觉得有点别扭,那就是如果有类似的去重需求,我们都要新增一个类去实现IEqualityComparer<TSource>接口,不够灵活,本着封装重用的原则,想了想能否在这方面进行优化。恰巧最近在搞一个Android项目,学习了一下java,了解到java有一个匿名实现接口的语法特性,如果C#也能匿名实现接口,那就不需要增加那么多类去实现接口,会方便很多。很遗憾C#中没有这个特性,看了下资料我感觉java其实也不算是真正意义上的匿名实现,它是编译器做了手脚,编译的时候生成了一个真实的类去实现接口。在一番查找资料后,终于找到了一个很好的解决方案:

public class LambdaComparer<T> : IEqualityComparer<T>
{
 private readonly Func<T, T, bool> _lambdaComparer;
 private readonly Func<T, int> _lambdaHash;
 public LambdaComparer(Func<T, T, bool> lambdaComparer)
 : this(lambdaComparer, EqualityComparer<T>.Default.GetHashCode)
 {
 }
 public LambdaComparer(Func<T, T, bool> lambdaComparer, Func<T, int> lambdaHash)
 {
 if (lambdaComparer == null)
  throw new ArgumentNullException("lambdaComparer");
 if (lambdaHash == null)
  throw new ArgumentNullException("lambdaHash");
  _lambdaComparer = lambdaComparer;
  _lambdaHash = lambdaHash;
 }

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

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

很巧妙的采用了泛型委托的方式,实现只需要定义一个类实现IEqualityComparer<TSource>接口,Equals、GetHashCode的实现,由传入的委托方法决定,接下来就简单了

var newList3 = list.Distinct(new LambdaComparer<User>((a, b) => a.Id == b.Id && a.Name == b.Name, obj => obj.ToString().GetHashCode())).ToList();

是不是很熟悉的写法,想怎么比较就怎么比较,方便快捷,不需要定义那么多类去实现接口,目的达到。Linq中有很多扩展方法,都会用到IEqualityComparer<TSource>接口。通过这种方式,可以大大提高重用率

参考资料

1、https://www.jb51.net/article/162602.htm

2、https://ask.helplib.com/c-Sharp/post_1277383

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对我们的支持。

(0)

相关推荐

  • C# Distinct和重写IEqualityComparer时要知道的二三事

    我们在想对一个可枚举的对象集合进行去重操作时,一般第一个想到的就是就是Linq的Distinct方法. 先定义一个类,然后使用Distinct方法去重 class Man { public int Age { get; set; } public string Name { get; set; } public string Adress { get; set; } public decimal Weight { get; set; } public decimal Height { get;

  • 利用Distinct()内置方法对List集合的去重问题详解

    前言 说到对集合去重处理,第一时间想到的肯定是Linq的Distinct扩展方式,对于一般的值类型集合去重,很好处理,直接list.Distinct()即可.但是如果想要对一个引用类型的集合去重(属性值都相同就认为重复),就会发现,直接Distinct()是不行的 先来看看泛型链表 List<T> 的定义: public class List<T> : IList<T>, ICollection<T>, IList, ICollection, IReadOn

  • 对python内置map和six.moves.map的区别详解

    python内置map返回的是列表,而six.moves.map返回的是iter. >>> map(lambda a: a*2, [1, 2, 3]) [2, 4, 6] >>> m = six.moves.map(lambda a: a*2, [1, 2, 3]) >>> type(m) <type 'itertools.imap'> >>> next(m) 2 >>> next(m) 4 >&g

  • 玩客云内置EMMC存储刷入Armbian系统(图文详解)

    目录 设备准备:玩客云 玩客云配置: 系统:Armbian 准备工具 刷机软件及系统准备 刷机 步骤一:连接设备 步骤二:导入镜像 步骤三:烧入安卓底包 步骤四:U盘写入 Armbian 步骤五:U盘写入系统 重启并连接设备 把系统写入EMMC 第三步1:拆机 最近因 Nas 负荷太大,搞了一个玩客云作为微型主机分担了部分压力.要让玩客云成为一台微型主机,需要给它安装一个Armbian系统. 设备准备:玩客云 玩客云是一款前些年很火的矿机,曾经在官网售卖¥599,现在已经沦落到¥45包邮的田地了

  • Python3.5常见内置方法参数用法实例详解

    本文实例讲述了Python3.5常见内置方法参数用法.分享给大家供大家参考,具体如下: Python的内置方法参数详解网站为:https://docs.python.org/3/library/functions.html?highlight=built#ascii 1.abs(x):返回一个数字的绝对值.参数可以是整数或浮点数.如果参数是一个复数,则返回它的大小. #内置函数abs() print(abs(-2)) print(abs(4.5)) print(abs(0.1+7j)) 运行结果

  • 11个Python3字典内置方法大全与示例汇总

    概述 在绝大部分的开发语言中与实际开发过程中,Dictionary扮演着举足轻重的角色.从我们的数据模型到服务器返回的参数到数据库的应用等等,Dictionary的身影无处不在. 在Python中,Dictionary是一种可变的容器类型.所谓容器类型,就是我们放置数据的地方.不同于List的有序.操作时对数据类型统一性的要求较严格,Dictionary是一种可变的.不限存储对象.无序的数据模型. 字典有着类似列表的高灵活度的特点,而与列表通过偏移索引的存取元素的方式不同,字典是通过无序的键来存

  • Python反射和内置方法重写操作详解

    本文实例讲述了Python反射和内置方法重写操作.分享给大家供大家参考,具体如下: isinstance和issubclass isinstance(obj,cls)检查是否obj是否是类 cls 的对象,类似 type() class Foo(object): pass obj = Foo() isinstance(obj, Foo) issubclass(sub, super)检查sub类是否是 super 类的派生类 class Foo(object): pass class Bar(Fo

  • Python3列表内置方法大全及示例代码小结

    Python中的列表是简直可说是有容乃大,虽然看似类似C中的数组,但是Python列表可以接受任意的对象元素,比如,字符串,数字,布尔值,甚至列表,字典等等,自由度提升到一个新的高度,而Python也提供了大量列表相关的内置方法来有效操作列表: 方法 描述 append 将单个对象添加至列表末尾 clear 删除列表中所有项目 copy 列表的浅拷贝,参见上篇文章<Python列表赋值,复制,深拷贝以及5种浅拷贝详解> count 统计指定对象在列表中出现的次数 extend 通过附加iter

  • Python序列对象与String类型内置方法详解

    本文实例讲述了Python序列对象与String类型内置方法.分享给大家供大家参考,具体如下: 前言 在Python数据结构篇中介绍了Python的序列类型数据结构,这次继续深入的学习序列和String类型对象的内建方法. 软件环境 系统 UbuntuKylin 14.04 软件 Python 2.7.3 IPython 4.0.0 序列类型 序列类型,即由整数进行索引的对象的有序集合.其中又可以分为下面两类: 可变序列:列表,支持元素的插入.删除.替换 不可变序列:元组.字符串 序列的操作方法

  • Python List列表对象内置方法实例详解

    本文实例讲述了Python List列表对象内置方法.分享给大家供大家参考,具体如下: 前言 在上一篇中介绍了Python的序列和String类型的内置方法,本篇继续学习作为序列类型成员之一的List类型的内置方法. 软件环境 系统 UbuntuKylin 14.04 软件 Python 2.7.3 IPython 4.0.0 列表List 列表是一种容器,存放内存对象的引用.即是任意内存对象的有序集合,不同的类型对象可以存放在同一个列表中.通过索引来访问其中的元素.可以任意的嵌套.伸长.异构.

  • Python内置方法实现字符串的秘钥加解密(推荐)

    在实际编程开发中,我们会使用到各类的加密算法来对数据和信息进行加密.比如密码中比较常见的MD5加密,以及AES加密等等. 对于密码认证来说,MD5加密是比较适合的,因为其不需要接触到明文的数据,只需要比对MD5加密后的哈希值就能判断数据是否一致:而对于一些在加密后进行解密的数据而言,AES则更加的常用. 在Python中实现AES算法需要借助的第三方库Crypto,其在各个操作系统上的安装方法有些许复杂,所以对于简单的使用有点杀鸡用牛刀的意思.在Mrdoc的开发过程中,我们就遇到了这样的问题.一

随机推荐