zl程序教程

您现在的位置是:首页 >  .Net

当前栏目

【.net 深呼吸】EqualityComparer——自定义相等比较

2023-03-20 14:46:30 时间

自定义实现两个对象的相等比较,一种方案是重写Object类的Equals方法,很easy,如果相等返回true,不相等就返回false。不过,如果把自定义相等的比较用于泛型集,比如Dictionary、HashSet等,这些集合都有一个共同点——必须标识存储项的唯一性,即每一个子项都有对应的key。

object.Equals方法是面向Object类型的,如果用于泛型对象,在判断是否相等的过程需要进行大量的装箱/拆箱操作,尤其是复合类型,由于要进行细致的比较,类型转换更为频繁,这样会带来一定量的性能开销,所以,对于泛型集合的相等比较,应该考虑使用 IEqualityComparer<T>,Dotnet类型提供了一个实现了该接口的抽象类——EqualityComparer<T>。

在实际使用中,不妨直接实现这个抽象类,好处是该抽象类公开了一个静态的Default属性,可以返回平台默认的比较方案。因此,实现该抽象类的好处在于,既可以提供自定义实现,同时也可以保留默认行为。

 

我们先来解释一下,为什么在泛型集合中需要用到自定义相等比较。看例子,咱们以常用的Dictionary为例,字典的Key我用一个叫Entity的类来标识,该类定义如下。

    public class Entity
    {
        public int ID { get; set; }
        public string Name { get; set; }
    }

 

然后,我们实例化一个字典,并向其中添加两个项。

            IDictionary<Entity, string> dic = new Dictionary<Entity, string>();

            dic.Add(new Entity { ID = 1, Name = "小明" }, "C++");
            dic.Add(new Entity { ID = 2, Name = "小王" }, "VB");

 

接着,从字典中读出Key为ID = 2 , Name = "小王" 的值。

            Entity findkey = new Entity
            {
                ID = 2,
                Name = "小王"
            };

            if (dic.ContainsKey(findkey))
            {
                Console.WriteLine(dic[findkey]);
            }

在查找时,先实例化一个Entity,然后给ID和Name属性赋上要查找的值,随后调用字典实例的ContainsKey方法判断一下要查找的key是否存在于字典中,如果存在,就输出该key对应的值。

 

代码看起来很完美,但一旦运行,你会发现什么都没找到。为啥呢?

因为该字典存储项的key是我们自定义的Entity类,当我们要从中查找时,是另外实例化了一个Entity对象,并赋了对应的属性值去查找的,可是问题就来了,我们实例化的findkey对象,与存入到字典中的Entity不是同一个对象,虽然它们的属性值相等,但它们引用的不是同一个实例,因为被判定为不相等的对象,故找不到对应的Key。

 

这个时候,EqualityComparer就派上用场了,自定义一个类并从它派生,添加自己的代码实现,不管是不是同一个对象实例,只要属性的值相等,则视为相同的key。

    public sealed class CustomEqComparer : EqualityComparer<Entity>
    {
        public override bool Equals(Entity x, Entity y)
        {
            if (x.ID == y.ID && x.Name == y.Name)
                return true;
            return false;
        }

        public override int GetHashCode(Entity obj)
        {
            return obj.ID.GetHashCode();
        }
    }

实现Equals方法,如果两个对象相等,返回真,否则返回假。GetHashCode方法返回哈希值,算法不应该过于复杂,避免性能开销,只要能够保证相等的两个对象返回相同的哈希值;不相等的对象返回不同的哈希值,这样就可以了。这里直接以ID属性的值为哈希,所以,每个key的ID值不能重复。

 

自定义完比较器后,只要在实例化字典实例时传给它的构造函数就可以了。把上面的代码改为:

            CustomEqComparer comp = new CustomEqComparer();
            IDictionary<Entity, string> dic = new Dictionary<Entity, string>(comp);

 

现在,再次执行前面的例子,ID=2,Name="小王"的key就可以查找出来了。

完整的演示代码如下:

    class Program
    {
        static void Main(string[] args)
        {
            CustomEqComparer comp = new CustomEqComparer();
            IDictionary<Entity, string> dic = new Dictionary<Entity, string>(comp);

            dic.Add(new Entity { ID = 1, Name = "小明" }, "C++");
            dic.Add(new Entity { ID = 2, Name = "小王" }, "VB");

            Entity findkey = new Entity
            {
                ID = 2,
                Name = "小王"
            };

            if (dic.ContainsKey(findkey))
            {
                Console.WriteLine(dic[findkey]);
            }


            Console.Read();
        }
    }

    public class Entity
    {
        public int ID { get; set; }
        public string Name { get; set; }
    }

    public sealed class CustomEqComparer : EqualityComparer<Entity>
    {
        public override bool Equals(Entity x, Entity y)
        {
            if (x.ID == y.ID && x.Name == y.Name)
                return true;
            return false;
        }

        public override int GetHashCode(Entity obj)
        {
            return obj.ID.GetHashCode();
        }
    }