前言

大家在想对三个可枚举的对象集合实行去重操作时,一般第贰个想到的正是正是Linq的Distinct方法。

标题引出:在骨子里中相遇3个难点,要拓展联谊去重,集合内储存的是引用类型,要求依照id举办去重。这一年linq
的distinct 就不够用了,对于引用类型,它直接相比地址。测试数据如下:

Enumerable.Distinct
方法 是常用的LINQ增添方法,属于System.Linq的Enumerable方法,可用于去除数组、集合中的重复成分,仍是能够自定义去重的规则。

List集合操作去除重复数据的那种意况日常会遭遇,新浪里面也有不少大神们做过,在那边境海关键是借鉴然后自身收十了一下,首假若为了有利于温馨,现在再次相遇那种去重难题,直接打开自个儿的链接拿起键盘正是干,,,,

先定义一个类,然后选取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,集合里照旧留存多少个Adam。

大家需求基于Person 的 ID 举行去重。当然使用linq Distinct
不满足,照旧有法子落到实处的,通过GroupBy先分一下组,再取第多个数据即可。例如:

 

 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()

第3个点子不带参数,第叁个点子必要传三个System.Collections.Generic.IEqualityComparer<T>的落到实处指标

二、方法二

于是大家对目的集合使用Distinct方法时要选拔重载Distinct<TSource>(this
IEnumerable<TSource> source, IEqualityComparer<TSource>
comparer);

通常通过GroupBy去贯彻也是足以的,毕竟在内部存款和储蓄器操作照旧极快的。但此间我们用其余主意去完成,并且找到最佳的落到实处格局。

一.值类别成分集合去重

运用Lamb达表明式Distinct方法,大家转到定义能够看来三个重载的Distinct方法,如图

要动用这些方法,我们得重写IEqualityComparer接口,再选用Distinct方法:

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

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

金沙注册送58 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接口

执行结果是:1 2 3 肆 5

我们再转到定义看看IEqualityComparer接口里面到底是个吗玩意儿,原来有多少个措施

可是,再贰回,distinct集合内照旧有五个目标。

IEnumerable<T> 的恢宏方法 Distinct 定义如下:

二.引用类型成分集合去重

金沙注册送58 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);

第叁自定义三个Student类

那儿大家来实现IEqualityComparer接口里面包车型客车章程

之所以IEqualityComparer内部会在选拔 Equals 前先使用
GetHashCode 方法,在八个对象的HashCode都1致时立刻判断目的相等。

金沙注册送58,能够看来,Distinct方法有二个参数为 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);
}

金沙注册送58 3

大家再来写二个通用的恢宏方法

是因为在上例中list中的多个引用实际上是七个不一样的对象,因而HashCode必定区别等

因而达成那些接口大家就能够落成和谐的比较器,定义本身的可比规则了。

    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,大家看最终实现的代码:

金沙注册送58 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对象了,成功促成了去重。

(补充1)在此以前未曾把扩展方法贴出来,而且看样子有朋友关系比较字符串忽略大小写的难点(其实下边有七个构造函数就足以解决这一个题材)。那里扩充方法能够写为:

            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忽略大小写比较,就能够写成:

推行结果:金沙注册送58 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接口的暗许相比器举办比较的,对于引用类型,默许相比较器相比的是其引述地址,程序中集合里的每3个因素都是个新的实例,引用地址都以见仁见智的,所以不会被视作重复记录删除掉。

 

二、通过哈希表。率先种做法的通病是不单要定义新的恢弘方法,还要定义三个新类。能还是不能唯有三个扩大方法就消除?可以,通过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 出处: 
本文版权归小编和今日头条共有,欢迎转发,但未经小编同意必须保留此段表明,且在篇章页面显然地方给出原作连接,不然保留追究法律义务的义务。要是以为还有扶助的话,能够点一下右下角的【推荐】,希望能够持续的为大家带来好的技巧小说!想跟自个儿联合提高么?那就【关切】笔者啊。

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()));

进行结果:金沙注册送58 6

咱俩依照Id去给这几个集合去重成功!

3.哪些编写二个存有扩大性的去重方法

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

金沙注册送58 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));
        }
    }

金沙注册送58 8

调用:

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

用到了泛型、委托、增添方法等知识点。能够用于别的聚众的各个去重场景

相关文章

网站地图xml地图