使用IEqualityComparer和Equals/GethashCode Override有什么区别?

26

有时候我使用字典时需要修改默认的Equals含义以便比较键。如果我在键的类上重写了Equals和GetHashCode,或者创建了一个实现IEqualityComparer接口的新类,那么我将得到相同的结果。那么使用IEqualityComparer和Equals/GethashCode覆盖之间有什么区别呢?两个例子:

class Customer
{
    public string name;
    public int age;
    public Customer(string n, int a)
    {
        this.age = a;
        this.name = n;
    }
    public override bool Equals(object obj)
    {
        Customer c = (Customer)obj;
        return this.name == c.name && this.age == c.age;
    }
    public override int GetHashCode()
    {
        return (this.name + ";" + this.age).GetHashCode();
    }
}
  class Program
{
    static void Main(string[] args)
    {
        Customer c1 = new Customer("MArk", 21);
        Customer c2 = new Customer("MArk", 21);
        Dictionary<Customer, string> d = new Dictionary<Customer, string>();
        Console.WriteLine(c1.Equals(c2));
        try
        {
            d.Add(c1, "Joe");
            d.Add(c2, "hil");
            foreach (KeyValuePair<Customer, string> k in d)
            {
                Console.WriteLine(k.Key.name + " ; " + k.Value);
            }
        }
        catch (ArgumentException)
        {
            Console.WriteLine("Chiave già inserita in precedenza");
        }
        finally
        {
            Console.ReadLine();
        }
    }
}

第二个:

class Customer
{
    public string name;
    public int age;
    public Customer(string n, int a)
    {
        this.age = a;
        this.name = n;
    }
}
class DicEqualityComparer : EqualityComparer<Customer>
{
    public override bool Equals(Customer x, Customer y) // equals dell'equalitycomparer
    {
        return x.name == y.name && x.age == y.age;
    }
    public override int GetHashCode(Customer obj)
    {
        return (obj.name + ";" + obj.age).GetHashCode();
    }
}
class Program
{
    static void Main(string[] args)
    {
        Customer c1 = new Customer("MArk", 21);
        Customer c2 = new Customer("MArk", 21);
        DicEqualityComparer dic = new DicEqualityComparer();
        Dictionary<Customer, string> d = new Dictionary<Customer, string>(dic);
        Console.WriteLine(c1.Equals(c2));
        try
        {
            d.Add(c1, "Joe");
            d.Add(c2, "hil");
            foreach (KeyValuePair<Customer, string> k in d)
            {
                Console.WriteLine(k.Key.name + " ; " + k.Value);
            }
        }
        catch (ArgumentException)
        {
            Console.WriteLine("Chiave già inserita in precedenza");
        }
        finally
        {
            Console.ReadLine();
        }
    }
}

这两个例子的结果相同。

提前感谢。


3
可能是重复问题/类似问题:https://dev59.com/rlzUa4cB1Zd3GeqP0i6Y该接口的作用是为了在.NET中实现自定义对象的比较。通常,使用默认的比较运算符来进行对象之间的比较,但是这种方式并不总是适用于所有情况。通过实现IEquatable接口,可以自定义比较的方式,并确保对象被正确地比较。 - Clint
4
因为比较某些对象有多种方式。 - Pragmateek
4个回答

21

当你重写 EqualsGetHashCode 方法时,你改变了对象判断是否与另一个对象相等的方式。需要注意的是,如果你使用 == 运算符比较对象,它的行为将不同于 Equals,除非你也重写运算符。

这样做会改变单个类的行为,如果你需要对其他类使用相同的逻辑,怎么办? 如果你需要进行一种“通用比较”,那么你可以使用 IEqualityComparer 接口。

看下面的例子:

interface ICustom
{
    int Key { get; set; }
}
class Custom : ICustom
{
    public int Key { get; set; }
    public int Value { get; set; }
}
class Another : ICustom
{
    public int Key { get; set; }
}

class DicEqualityComparer : IEqualityComparer<ICustom>
{
    public bool Equals(ICustom x, ICustom y)
    {
        return x.Key == y.Key;
    }

    public int GetHashCode(ICustom obj)
    {
        return obj.Key;
    }
}

我有两个不同的类,它们都可以使用相同的比较器。

var a = new Custom { Key = 1, Value = 2 };
var b = new Custom { Key = 1, Value = 2 };
var c = new Custom { Key = 2, Value = 2 };
var another = new Another { Key = 2 };

var d = new Dictionary<ICustom, string>(new DicEqualityComparer());

d.Add(a, "X");
// d.Add(b, "X"); // same key exception
d.Add(c, "X");
// d.Add(another, "X"); // same key exception

注意到我不必在任何一个类中覆盖 EqualsGetHashCode。 我可以在任何实现了 ICustom 的对象中使用此比较器,而无需重写比较逻辑。 我还可以为“父类”创建一个 IEqualityComparer 并将其用于继承的类。 我可以创建一个行为不同的比较器,比如一个只比较 Value 而不是 Key 的比较器。

因此,IEqualityComparer 提供了更多的灵活性,可以实现通用解决方案。


16
简而言之,IEqualityComparer将比较逻辑外化,而覆盖Equals/GetHashCode方法则将其内部化 - 对于IComparable(内部化)和IComparer(外部化)也是相同的原理/区别。 - h9uest
1
@h9uest:说得好。我想知道是否也可以将IEqualityComparer设为internal。Equals/GetHashCode不仅内部化了比较逻辑,而且还全局化了它们。可能有些情况下我只想进行一次内部比较(不使用集合)。 - liang
@liang,恐怕IEqualityComparer是为了外部化比较而设计的。通常我会编写一个实现IEqualityComparer接口的MyCustomeComparer类,并将一个MyCustomeComparer对象传递给需要它的任何对象 - 我相信你已经知道这种用法了。 - h9uest
@liang 另外,我不确定为什么您想要将比较逻辑内部化仅仅是为了一次。通过“内部化”,您希望比较逻辑成为类的固有部分 - 毕竟,每个派生类默认都会具有比较逻辑!所以您可能想要微调您的模型?是吗?不是吗? - h9uest
这是正确答案的50%,另外50%是在不同情况下,一个对象可能与另一个对象相等,但在其他情况下可能不相等。例如,你有一个人类,如果人A和人B都是男性,那么在性别搜索中它们可能被归类为相等,在这种情况下重写object.Equals将是一个非常糟糕的主意。 - MikeT
显示剩余5条评论

4
该对象的Equals()GetHashCode()实现了对象内在的等价概念。然而,您可能想要使用其他等价概念 - 例如,一个地址对象的等价比较器只使用邮政编码而不是完整的地址。

1

对于这个目的来说,实质上是相同的,但有一个微妙的区别。在第一个示例中,您使用类型为Object的参数覆盖Equals,然后必须将其转换为Customer,但在第二个示例中,您可以将参数设置为Customer类型,这意味着无需转换。

这意味着重写Equals允许比较不同类型的两个对象(在某些情况下可能需要),但实现IEqualityComparer不具有此自由度(在某些情况下也可能需要)。


1
有许多情况下,一个人可能希望使用Dictionary时,使用的不是100%等价的对象。举个简单的例子,一个人可能希望拥有一个大小写不敏感的字典。实现这一点的方法之一是在将字符串存储在字典中或执行查找之前,将字符串转换为规范的大写形式。另一种方法是向字典提供一个IEqualityComparer<string>,它将计算哈希码并在某种不区分大小写的函数中检查相等性。有些情况下,将字符串转换为规范形式并尽可能使用该形式将更有效,但在其他情况下,仅存储字符串的原始形式更有效。我希望.NET拥有的一个功能将提高这些字典的有用性,即请求与给定键相关联的实际键对象的方法(因此,如果字典包含字符串"WowZo"作为键,则可以查找"wowzo"并获得"WowZo";不幸的是,如果TValue不包含冗余引用,检索实际键对象的唯一方法是枚举整个集合)。
另一种情况下,当一个对象持有对可变类型实例的引用,但永远不会将该实例暴露给可能会改变它的任何东西时,拥有替代比较手段可能是有用的。一般来说,持有相同值序列的两个int[]实例将不可互换,因为将来可能会更改其中一个或两个以持有不同的值。另一方面,如果一个字典将被用于保存和查找int[]值,每个值都是int[]实例在宇宙中唯一的引用,并且如果没有实例被修改或暴露给外部代码,那么将相等的数组实例视为相等可能是有用的。由于Array.Equals测试严格等价性(引用相等性),因此需要使用其他方法来测试数组的等价性。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接