List对象去重碎碎念之神叨叨,不够用了

前言

小编们在想对多个可枚举的目的集合实行去重操作时,一般第3个想到的正是正是Linq的Distinct方法。

标题引出:在实际上中碰着叁个题材,要实行联谊去重,集合内储存的是引用类型,需求依据id进行去重。那年linq
的distinct 就不够用了,对于引用类型,它直接相比地址。测试数据如下:

Enumerable.Distinct
方法 是常用的LINQ扩展方法,属于System.Linq的Enumerable方法,可用于去除数组、集合中的重复元素,还足以自定义去重的平整。

List集合操作去除重复数据的那种气象日常会赶上,果壳网里面也有为数不少大神们做过,在这里首如若借鉴然后自身收10了弹指间,首若是为了方便本人,以往重新遇到那种去重难题,间接打开本身的链接拿起键盘正是干,,,,

先定义八个类,然后使用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"}                
    }; 

有七个重载方法:

一、方法一

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; set; }
        }

List<Man> list = new List<Man>()
            { 
            new Man(){Age=21,Name="Adam",Adress="Shenzhen",Weight=60,Height=170},
            new Man(){Age=21,Name="Adam",Adress="Shenzhen",Weight=60,Height=170}
            };
            var distinct = list.Distinct();

 

        //
        // 摘要: 
        //     通过使用默认的相等比较器对值进行比较返回序列中的非重复元素。
        //
        // 参数: 
        //   source:
        //     要从中移除重复元素的序列。
        //
        // 类型参数: 
        //   TSource:
        //     source 中的元素的类型。
        //
        // 返回结果: 
        //     一个 System.Collections.Generic.IEnumerable<T>,包含源序列中的非重复元素。
        //
        // 异常: 
        //   System.ArgumentNullException:
        //     source 为 null。
        public static IEnumerable<TSource> Distinct<TSource>(this IEnumerable<TSource> source);
        //
        // 摘要: 
        //     通过使用指定的 System.Collections.Generic.IEqualityComparer<T> 对值进行比较返回序列中的非重复元素。
        //
        // 参数: 
        //   source:
        //     要从中移除重复元素的序列。
        //
        //   comparer:
        //     用于比较值的 System.Collections.Generic.IEqualityComparer<T>。
        //
        // 类型参数: 
        //   TSource:
        //     source 中的元素的类型。
        //
        // 返回结果: 
        //     一个 System.Collections.Generic.IEnumerable<T>,包含源序列中的非重复元素。
        //
        // 异常: 
        //   System.ArgumentNullException:
        //     source 为 null。
        public static IEnumerable<TSource> Distinct<TSource>(this IEnumerable<TSource> source, IEqualityComparer<TSource> comparer);    

选取HashSet去重,在实体类里重写Equals和GetHashCode方法

而是去重得到的distinct集合的Count依旧为2,集合里仍旧存在七个艾达m。

咱俩供给依据Person 的 ID 进行去重。当然使用linq Distinct
不满意,照旧有艺术落到实处的,通过GroupBy先分一下组,再取第3个数据即可。例如:

 

 class Program
    {
        static void Main(string[] args)
        {
            List<UserInfo> list = new List<UserInfo>()
            {
                new UserInfo() {  Id="1", UserName="111"},
                new UserInfo() {  Id="1", UserName="111"}
            };

            // 因为我们通常操作的是List集合,所以我在这里把list转成了HashSet类型来接受
            HashSet<UserInfo> hs = new HashSet<UserInfo>(list); 
            foreach (var item in hs)
            {
                Console.WriteLine($"Id:{item.Id}  UserName:{item.UserName}");
            }

            Console.ReadKey();
        }

        class UserInfo
        {
            public string Id { get; set; }
            public string UserName { get; set; }

            public override bool Equals(object obj)
            {
                UserInfo user = obj as UserInfo;
                return this.Id == user.Id ;
            }
            public override int GetHashCode()
            {
                return this.Id.GetHashCode();
            }
        }
    }

实在,Distinct方法内开始展览相比的是宣称的引用,而不是指标属性,就和对两脾本性壹模1样的对象使用Equals()方法获得的是False壹样。

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

首先个艺术不带参数,第2个格局供给传三个System.Collections.Generic.IEqualityComparer<T>的贯彻指标

二、方法二

之所以大家对目的集合使用Distinct方法时要动用重载Distinct<TSource>(this
IEnumerable<TSource> source, IEqualityComparer<TSource>
comparer);

平时通过GroupBy去实现也是足以的,终归在内存操作还是相当慢的。但那里大家用别的秘诀去落到实处,并且找到最佳的兑现格局。

一.值体系元素集合去重

利用拉姆达表明式Distinct方法,大家转到定义能够见到多少个重载的Distinct方法,如图

要选择这些格局,我们得重写IEqualityComparer接口,再使用Distinct方法:

 

List<int> list = new List<int> { 1, 1, 2, 2, 3, 4, 5, 5 };
list.Distinct().ToList().ForEach(s => Console.WriteLine(s));

美高梅开户网址 1

public class ManComparer : IEqualityComparer<Man>
        {
            public bool Equals(Man x, Man y)
            {
                return x.Age == y.Age
                    && x.Name == y.Name
                    && x.Adress == y.Adress
                    && x.Weight == y.Weight
                    && x.Height == y.Height;
            }

            public int GetHashCode(Man obj)
            {
                return obj.GetHashCode();
            }
        }

 var distinct = list.Distinct(new ManComparer());

一、通过IEqualityComparer接口

List对象去重碎碎念之神叨叨,不够用了。推行结果是:一 贰 3 4 五

大家再转到定义看看IEqualityComparer接口里面到底是个啥东西,原来有多个法子

可是,再壹遍,distinct集合内还是有多个指标。

IEnumerable<T> 的增添方法 Distinct 定义如下:

二.引用类型元素集合去重

美高梅开户网址 2

实则,由于平素得到对象的HashCode,用HashCode进行相比的快慢比 Equals
方法越来越快,

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

首先自定义2个Student类

此时大家来促成IEqualityComparer接口里面包车型地铁章程

之所以IEqualityComparer内部会在利用 Equals 前先采取GetHashCode 方法,在七个目的的HashCode都壹样时立刻判断目的相等。

能够见到,Distinct方法有3个参数为 IEqualityComparer<T>
的重载。该接口的定义如下:

   public class Student
    {
        public string Name { get; private set; }
        public int Id { get; private set; }
        public string Hobby { get; private set; }
        public Student(string name, int id, string hobby)
        {
            this.Name = name;
            this.Id = id;
            this.Hobby = hobby;
        }
        /// <summary>
        /// 方便输出,重写ToString方法
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return string.Format("{0}\t{1}\t{2}", this.Name, this.Id, this.Hobby);
        }
    }
class Program
    {
        static void Main(string[] args)
        {

            UserInfo info = new UserInfo();
            List<UserInfo> list = new List<UserInfo>()
            {
                new UserInfo() {  Id="1", UserName="111"},
                new UserInfo() {  Id="1", UserName="111"}
            };

            list.Distinct(new DistinctCompare()).ToList().ForEach(item =>
            {

                Console.WriteLine($"Id:{item.Id}  UserName:{item.UserName}");
            });

            Console.ReadKey();
        }


        class UserInfo
        {
            public string Id { get; set; }
            public string UserName { get; set; }
        }


        class DistinctCompare : IEqualityComparer<UserInfo>
        {
            public bool Equals(UserInfo x, UserInfo y)
            {
                return x.Id == y.Id;//可以自定义去重规则,此处将Id相同的就作为重复记录
            }
            public int GetHashCode(UserInfo obj)
            {
                return obj.Id.GetHashCode();
            }
        }
    }

而当五个目的HashCode分化时, Equals
方法就会被调用,对要对比的指标进行判断。

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

美高梅开户网址 3

我们再来写1个通用的扩张方法

鉴于在上例中list中的多个引用实际上是七个不等的靶子,由此HashCode必定不1致

透过落实那一个接口我们就能够兑现自身的比较器,定义本人的可比规则了。

    public class Student
    {
        public string Name { get; private set; }
        public int Id { get; private set; }
        public string Hobby { get; private set; }
        public Student(string name, int id, string hobby)
        {
            this.Name = name;
            this.Id = id;
            this.Hobby = hobby;
        }
        /// <summary>
        /// 方便输出,重写ToString方法
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return string.Format("{0}\t{1}\t{2}", this.Name, this.Id, this.Hobby);
        }
    }

帮助类

所以要触发Equlas方法,大家必要改 GetHashCode
,让它回到相同的常量

那边有1个难题,IEqualityComparer<T> 的 T
是要比较的指标的花色,在此间便是 Person,那那里怎么去赢得 Person 的性能id呢?恐怕说,对于其余项目,小编如何领会要相比的是哪位属性?答案正是:委托。通过委托,要相比较什么性质由外部钦赐。那也是linq
扩大方法的规划,参数都以寄托项目标,也等于平整由外部定义,内部只担负调用。ok,大家看最终完毕的代码:

美高梅开户网址 4

public static class DistinctHelper
    {
        /// <summary>
        /// 自定义Distinct扩展方法
        /// </summary>
        /// <typeparam name="T">要去重的对象类</typeparam>
        /// <typeparam name="C">自定义去重的字段类型</typeparam>
        /// <param name="source">要去重的对象</param>
        /// <param name="getfield">获取自定义去重字段的委托</param>
        /// <returns></returns>
        public static IEnumerable<T> DistinctEx<T, C>(this IEnumerable<T> source, Func<T, C> getfield)
        {
            return source.Distinct(new Compare<T, C>(getfield));
        }
    }

    public class Compare<T, C> : IEqualityComparer<T>
    {
        private Func<T, C> _getField;
        public Compare(Func<T, C> getfield)
        {
            this._getField = getfield;
        }
        public bool Equals(T x, T y)
        {
            return EqualityComparer<C>.Default.Equals(_getField(x), _getField(y));
        }
        public int GetHashCode(T obj)
        {
            return EqualityComparer<C>.Default.GetHashCode(this._getField(obj));
        }
    }
public class ManComparerNew : IEqualityComparer<Man>
        {
            public bool Equals(Man x, Man y)
            {
                return x.Age == y.Age
                    && x.Name == y.Name
                    && x.Adress == y.Adress
                    && x.Weight == y.Weight
                    && x.Height == y.Height;
            }

            public int GetHashCode(Man obj)
            {
                return 1;
            }
        }

var distinct = list.Distinct(new ManComparerNew());
    //通过继承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));
        }
    }

选拔不到参数的Distinct方法去重

 实现

 

 

 

 class Program
    {
        static void Main(string[] args)
        {

            UserInfo info = new UserInfo();
            List<UserInfo> list = new List<UserInfo>()
            {
                new UserInfo() {  Id="1", UserName="111"},
                new UserInfo() {  Id="1", UserName="111"}
            };
            list.DistinctEx(x => x.Id).ToList().ForEach(item => {

                Console.WriteLine($"Id:{item.Id}  UserName:{item.UserName}");
            });
            Console.ReadKey();
        }


        class UserInfo
        {
            public string Id { get; set; }
            public string UserName { get; set; }
        }
}

明日distinct集合中就唯有二个Man对象了,成功落到实处了去重。

(补充一)此前未有把增添方法贴出来,而且看样子有对象关系比较字符串忽略大小写的标题(其实上边有八个构造函数就足以解决那个难点)。那里扩大方法能够写为:

            List<Student> list = new List<Student>() { 
                new Student("James",1,"Basketball"),
                new Student("James",1,"Basketball"),
                new Student("Kobe",2,"Basketball"),
                new Student("Curry",3,"Football"),
                new Student("Curry",3,"Yoga")
            };
            list.Distinct().ToList().ForEach(s => Console.WriteLine(s.ToString()));   

 三、方法三

 

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

 

行使Lamb达表达式一句话即可

            `
           

譬如,要基于Person的Name忽略大小写比较,就足以写成:

实施结果:美高梅开户网址 5

class Program
    {
        static void Main(string[] args)
        {

            List<UserInfo> list = new List<UserInfo>()
            {
                new UserInfo() {  Id="1", UserName="111"},
                new UserInfo() {  Id="1", UserName="111"}
            };

            // 用拉姆达表达式一句话即可实现去重
            var result = list.Where((x,i)=>list.FindIndex(y=>y.Id==x.Id)==i);

            result.ToList().ForEach(item=> 
            {
                Console.WriteLine($"Id:{item.Id}  UserName:{item.UserName}");
            });

            Console.ReadKey();
        }

        class UserInfo
        {
            public string Id { get; set; }
            public string UserName { get; set; }
        }

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

足见,并不曾去除重复的笔录。

 

 

不带comparer参数的Distinct方法是行使的IEqualityComparer接口的默许相比较器实行相比的,对于引用类型,暗许比较器比较的是其引述地址,程序中集合里的每三个成分都以个新的实例,引用地址都是差别的,所以不会被用作重复记录删除掉。

 

2、通过哈希表。首先种做法的弱点是不单要定义新的增加方法,还要定义一个新类。能否只有1个扩大方法就解决?能够,通过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);
        }

新建三个类,达成IEqualityComparer接口。注意GetHashCode方法的贯彻,唯有HashCode相同才会去比较

 

 

 

职分申明

美高梅开户网址 ,三、重写object方法。能还是不可能连扩充方法也决不了?能够。我们领略 object
是拥有品种的基类,当中有七个虚方法,Equals、GetHashCode,默许景况下,.net
正是经过那多个格局进行对象间的比较的,那么linq 无参的Distinct
是还是不是也是依据那四个法子来展开判断的?大家在Person里 override
那八个办法,并贯彻协调的可比规则。打上断点调节和测试,发以往进行Distinct时,是会跻身到那多个主意的。代码如下:

    public class Compare:IEqualityComparer<Student>
    {
        public bool Equals(Student x,Student y)
        {
            return x.Id == y.Id;//可以自定义去重规则,此处将Id相同的就作为重复记录,不管学生的爱好是什么
        }
        public int GetHashCode(Student obj)
        {
            return obj.Id.GetHashCode();
        }
    }

作者:SportSky 出处: 
本文版权归作者和博客园共有,欢迎转发,但未经小编同意必须保留此段表明,且在小说页面显明地点给出原作连接,否则保留追究法律权利的权利。假如觉得还有支持的话,能够点一下右下角的【推荐】,希望能够持续的为我们带来好的技术小说!想跟本身1块儿进步么?那就【关切】小编呢。

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去重的,所以第两种艺术提供了最优雅的落到实处。即使是其余意况,用后边的措施更通用。

下一场调用

list.Distinct(new Compare()).ToList().ForEach(s => Console.WriteLine(s.ToString()));

施行结果:美高梅开户网址 6

大家依照Id去给那么些集合去重成功!

三.怎么着编写2个富有扩充性的去重方法

public class Compare<T, C> : IEqualityComparer<T>
    {
        private Func<T, C> _getField;
        public Compare(Func<T, C> getfield)
        {
            this._getField = getfield;
        }
        public bool Equals(T x, T y)
        {
            return EqualityComparer<C>.Default.Equals(_getField(x), _getField(y));
        }
        public int GetHashCode(T obj)
        {
            return EqualityComparer<C>.Default.GetHashCode(this._getField(obj));
        }
    }
    public static class CommonHelper
    {
        /// <summary>
        /// 自定义Distinct扩展方法
        /// </summary>
        /// <typeparam name="T">要去重的对象类</typeparam>
        /// <typeparam name="C">自定义去重的字段类型</typeparam>
        /// <param name="source">要去重的对象</param>
        /// <param name="getfield">获取自定义去重字段的委托</param>
        /// <returns></returns>
        public static IEnumerable<T> MyDistinct<T, C>(this IEnumerable<T> source, Func<T, C> getfield)
        {
            return source.Distinct(new Compare<T, C>(getfield));
        }
    }

美高梅开户网址 7

    public class Compare<T, C> : IEqualityComparer<T>
    {
        private Func<T, C> _getField;
        public Compare(Func<T, C> getfield)
        {
            this._getField = getfield;
        }
        public bool Equals(T x, T y)
        {
            return EqualityComparer<C>.Default.Equals(_getField(x), _getField(y));
        }
        public int GetHashCode(T obj)
        {
            return EqualityComparer<C>.Default.GetHashCode(this._getField(obj));
        }
    }
    public static class CommonHelper
    {
        /// <summary>
        /// 自定义Distinct扩展方法
        /// </summary>
        /// <typeparam name="T">要去重的对象类</typeparam>
        /// <typeparam name="C">自定义去重的字段类型</typeparam>
        /// <param name="source">要去重的对象</param>
        /// <param name="getfield">获取自定义去重字段的委托</param>
        /// <returns></returns>
        public static IEnumerable<T> MyDistinct<T, C>(this IEnumerable<T> source, Func<T, C> getfield)
        {
            return source.Distinct(new Compare<T, C>(getfield));
        }
    }

美高梅开户网址 8

调用:

list.MyDistinct(s=>s.Id).ToList().ForEach(s => Console.WriteLine(s.ToString()));

用到了泛型、委托、扩大方法等知识点。能够用于别的聚众的种种去重场景

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图