[C#]关于Distinct与重写IEqualityComparer时得知道的二三

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();
    List<RoomEntity2> result2 = new List<RoomEntity2>();
            HashSet<string> hashSet = new HashSet<string>();
            foreach (var a in sourceList)
            {
                //StringBuilder sb = new StringBuilder();
                //sb.Append(a.Hotel);
                //sb.Append("_");
                //sb.Append(a.Room);
                //sb.Append("_");
                //sb.Append(a.EffectDate.ToString("yyyy-MM-dd"));//耗性能

                string key = a.Hotel + "^" + a.Room + "^" + a.EffectDate.ToString("yyyy-MM-dd");  //拼接耗性能
                if (hashSet.Add(key))
                {
                    result2.Add(new RoomEntity2
                    {
                        Hotel = a.Hotel,
                        Room = a.Room,
                        EffectDate = a.EffectDate
                    });
                }
            }
            watch.Stop();

            Console.WriteLine("数量:" + result2.Count + "耗时:" + watch.ElapsedMilliseconds);

实际上,Distinct方法内进行比较的是声明的引用,而不是对象属性,就和对两个属性一模一样的对象使用Equals()方法得到的是False一样。

于是试着改良一下,也使用匿名对象来进行哈希。

            `            

因为匿名类型上的 EqualsGetHashCode 方法是根据各属性的 Equals 和 GetHashcode 定义的,因此仅当同一匿名类型的两个实例的所有属性都相等时,这两个实例才相等。

 

下面是测试例子

然而去重得到的distinct集合的Count依然为二,集合里依然存在两个Adam。

Linq 的Distinct方法需要传递一个自己实现IEqualityComparer<TSource>金沙官网线上,的类来作为比较器。

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

个人比较偏好,自己实现一个去重方法——基于哈希表来判断是否有重复数据。

先定义一个类,然后使用Distinct方法去重

对于两个具有相同内容,但是不同地址引用的对象 ,Equals()将返回false,GetHashCode() 也不相等。这样就不能达到去重的目的。

要使用这个方法,我们得重写IEqualityComparer接口,再使用Distinct方法:

这段代码对IEnumerable<T>进行了扩展,然后实现一个IEqualityComparer<T>接口的比较器 ,并增加了一个Func<T, V> keySelector 可以用来选择需要去重的字段。

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

 

而当两个对象HashCode不相同时, Equals 方法就会被调用,对要比较的对象进行判断。

如果不指定实现,则使用默认的泛型相等比较器 EqualityComparer.Default。一般我们去重的字段都基于 数值 ,字符和时间类型,默认的相等比较器应该能满足需求。

现在distinct集合中就只有一个Man对象了,成功实现了去重。

  返回结果数量:300000 耗时:193 并没有达到去重目的。

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

里面用到了哈希表,检索速度取决于为 TKey 指定的类型的哈希算法的质量。 需要一个相等实现来确定键是否相等,可以使用一个接受 comparer 参数的构造函数来指定 IEqualityComparer 泛型接口的实现;

所以要触发Equlas方法,我们需要改 GetHashCode ,让它返回相同的常量

 

 

  返回结果数量:150000 耗时:140 性能会比LINQ 好。这种写法还是要注意的地方:匿名类型的字段中不能有引用类型。除非该引用类型实现了正确的EqualsGetHashCode 方法。

实际上,由于直接获取对象的HashCode,用HashCode进行比较的速度比 Equals 方法更快,

 

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

参考:

由于在上例中list中的两个引用实际上是两个不同的对象,因此HashCode必定不相同

定义两个实体

因此IEqualityComparer内部会在使用 Equals 前先使用 GetHashCode 方法,在两个对象的HashCode都相同时即刻判断对象相等。

  使用扩展方法去重

然而,再一次,distinct集合内依然有两个对象。

Stopwatch watch = new System.Diagnostics.Stopwatch();
            List<RoomEntity2> result = new List<RoomEntity2>();
            //linq distinct
            watch.Start();
            var temp = sourceList.Distinct(p => new { Hotel = p.Hotel, Room = p.Room, EffectDate = p.EffectDate });
            foreach (var a in temp)
            {
                result.Add(new RoomEntity2
                {
                    Hotel = a.Hotel,
                    Room = a.Room,
                    EffectDate = a.EffectDate
                });
            }
            watch.Stop();
            Console.WriteLine("数量:" + result.Count + "耗时:" + watch.ElapsedMilliseconds);

 

今天看到别人的代码使用 Linq 的Distinct进行去重。发现有很多需要注意的地方。

本文由金沙官网线上发布于编程,转载请注明出处:[C#]关于Distinct与重写IEqualityComparer时得知道的二三

您可能还会对下面的文章感兴趣: