Distinct方法去重,List对象去重碎碎念之神叨叨

By admin in 4858.com on 2019年3月25日

前言

我们在想对三个可枚举的指标集合进行去重操作时,一般第2个想到的正是正是Linq的Distinct方法。

标题引出:在实际上中碰着2个题材,要实行联谊去重,集合内囤积的是引用类型,要求依据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如故为二,集合里照旧存在多个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方法内举行相比的是声称的引用,而不是目的属性,就和对七个属性一模一样的靶子使用Equals()方法取得的是False一样。

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

第三个法子不带参数,第二个办法供给传二个System.Collections.Generic.IEqualityComparer<T>的贯彻目的

二、方法二

因此我们对目的集合使用Distinct方法时要运用重载Distinct<TSource>(this
IEnumerable<TSource> source, IEqualityComparer<TSource>
comparer);

普通通过GroupBy去落到实处也是足以的,毕竟在内部存款和储蓄器操作照旧极快的。但此间大家用别的点子去落成,并且找到最佳的兑现格局。

1.值品类成分集合去重

Distinct方法去重,List对象去重碎碎念之神叨叨。选取Lamb达表明式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));

4858.com 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 4 5

4858.com,我们再转到定义看看IEqualityComparer接口里面毕竟是个什么东西,原来有三个法子

只是,再二回,distinct集合内依旧有多少个指标。

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

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

4858.com 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);

第③自定义3个Student类

那会儿大家来促成IEqualityComparer接口里面包车型大巴章程

就此IEqualityComparer内部会在行使 Equals 前先利用
GetHashCode 方法,在八个对象的HashCode都一模一样时即刻判断目的相等。

能够见到,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);
}

4858.com 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,我们看最终完毕的代码:

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

执行结果:4858.com 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接口的私下认可相比器实行相比较的,对于引用类型,暗中认可相比器相比较的是其引用地址,程序中集合里的每三个要素都是个新的实例,引用地址都以例外的,所以不会被看成重复记录删除掉。

 

二 、通过哈希表。第②种做法的短处是不仅要定义新的壮大方法,还要定义三个新类。能否唯有三个恢宏方法就解决?能够,通过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()));

进行结果:4858.com 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));
        }
    }

4858.com 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));
        }
    }

4858.com 8

调用:

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

用到了泛型、委托、扩张方法等知识点。能够用来其余聚众的各样去重场景

发表评论

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

网站地图xml地图
Copyright @ 2010-2019 美高梅手机版4858 版权所有