В чем разница между использованием IEqualityComparer и Equals/GethashCode Override?

Когда я использую словари, мне иногда приходится менять значение Equals по умолчанию для сравнения ключей. Я вижу, что если я переопределю Equals и GetHashCode в классе ключей или создам новый класс, который реализует IEqualityComparer, у меня будет тот же результат. Итак, какая разница между использованием IEqualityComparer и Equals/GethashCode Override? Два примера:

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

}

Оба примера имеют одинаковый результат.

Спасибо заранее.

Ответ 1

При переопределении Equals и GetHashCode вы меняете способ определения объекта, если он равен другому. И обратите внимание, что если вы сравниваете объекты с помощью оператора ==, он не будет иметь такое же поведение, как 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

Обратите внимание, что мне не пришлось переопределять Equals, GetHashCode ни в одном из классов. Я могу использовать этот компаратор в любом объекте, который реализует ICustom, не переписывая логику сравнения. Я также могу сделать IEqualityComparer для "родительского класса" и использовать для наследуемых классов. У меня может быть сравнитель, который будет вести себя по-другому, я могу сравнить его Value вместо Key.

Итак, IEqualityComparer обеспечивает большую гибкость, и вы можете реализовать общие решения.

Ответ 2

Объект Equals() anf GetHashCode() реализует концепцию равенства, присущего объекту. Однако вы можете использовать альтернативные концепции равенства - например, сопоставление равенства для адресных объектов, которое использует только почтовый индекс, а не полный адрес.

Ответ 3

По сути, это одно и то же с одной тонкой разницей. В первом примере вы переопределяете Equals с использованием параметра типа Object, а затем должны отдать его Заказчику, однако в вашем втором примере вы можете иметь параметр типа Customer, что означает, что нет необходимости бросать.

Это означает, что переопределение Equals позволяет сравнивать между двумя объектами разных типов (что может потребоваться при определенных обстоятельствах), однако реализация IEqualityComparer не дает этой свободы (что также может потребоваться при определенных обстоятельствах).

Ответ 4

Существует много случаев, когда нужно, чтобы объекты Dictionary находили объекты с использованием эквивалента, отличного от 100%. В качестве простого примера может потребоваться словарь, который будет соответствовать не зависящим от регистра образом образом. Один из способов добиться этого - преобразовать строки в каноническую форму верхнего регистра, прежде чем хранить их в словаре или выполнить поиск. Альтернативный подход заключается в том, чтобы снабдить словарь IEqualityComparer<string>, который будет вычислять хэш-коды и проверять равенство в какой-то независимой от случая функции. Существуют некоторые обстоятельства, при которых преобразование строк в каноническую форму и использование этой формы по возможности будет более эффективным, но есть и другие, где более эффективно хранить строку в ее исходной форме. Одна из особенностей, которую я хотел бы пожелать .NET, которая могла бы повысить полезность таких словарей, была бы средством запроса фактического ключевого объекта, связанного с данным ключом (поэтому, если словарь содержал строку "WowZo" в качестве ключа, можно было бы искать "WowZo" и получить "WowZo", к сожалению, единственный способ получить фактический ключевой объект, если TValue не содержит избыточной ссылки на него, - это перечислить всю коллекцию).

Другой сценарий, когда может быть полезным иметь альтернативный способ сравнения, - это когда объект содержит ссылку на экземпляр изменяемого типа, но никогда не будет подвергать этот экземпляр чему-либо, что может его изменить. В общем случае два экземпляра int[], которые содержат одну и ту же последовательность значений, не будут взаимозаменяемыми, так как было бы возможно, что в будущем один или оба из них могут быть изменены для хранения разных значений. С другой стороны, если словарь будет использоваться для хранения и поиска значений int[], каждый из которых будет единственной ссылкой в ​​любом месте юниверса на экземпляр int[], и если ни один из экземпляров не будет изменен и не подвергаются внешнему коду, может быть полезно рассматривать в качестве равных экземпляров массива, которые содержат идентичные последовательности значений. Поскольку Array.Equals проверяет строгую эквивалентность (ссылочное равенство), необходимо будет использовать некоторые другие средства тестирования массивов для эквивалентности.