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

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

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

}

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

Заранее спасибо.


person dariush624    schedule 16.06.2013    source источник
comment
Возможный обман/аналог: stackoverflow. com/questions/7751170/   -  person Clint    schedule 17.06.2013
comment
Потому что есть несколько способов сравнить некоторые объекты.   -  person Pragmateek    schedule 17.06.2013


Ответы (4)


Когда вы переопределяете 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 обеспечивает большую гибкость, и вы можете реализовывать общие решения.

person BrunoLM    schedule 29.06.2013
comment
Проще говоря, IEqualityComparer выводит на внешний уровень логику сравнения, в то время как переопределение Equals/GetHashCode усваивает их. Тот же принцип/различие для IComparable(internalize) и IComparer(Externalize). - person h9uest; 05.12.2013
comment
@h9uest: хорошо сказано. Интересно, можно ли также интернализировать IEqualityComparer. Equals/GetHashCode не только усваивает логику сравнения, но и глобализует ее. Могут быть случаи, когда мне нужно внутреннее сравнение (без использования коллекции) только один раз. - person liang; 07.12.2013
comment
@liang Боюсь, IEqualityComparer был разработан для внешнего сравнения. Обычно я пишу MyCustomeComparer, который реализует IEqualityComparer и передает объект MyCustomeComparer любым объектам, которые в нем нуждаются — я уверен, что вы знаете об этом использовании. - person h9uest; 09.12.2013
comment
@liang Кроме того, я не уверен, почему вы хотите усвоить логику сравнения только один раз. Под «интернализацией» вы хотите, чтобы логика сравнения была неотъемлемой частью класса — в конце концов, каждый производный класс будет иметь логику сравнения по умолчанию! Значит, есть вероятность, что вы хотите немного изменить свою модель? да? нет? - person h9uest; 09.12.2013
comment
@h9uest, к каким объектам вы передаете MyCustomeComparer? Коллекции, подобные приведенному выше ответу? - person liang; 11.12.2013
comment
@h9uest Причина необходимости сравнения объектов только один раз аналогична коллекциям, возможно, разные объекты одного типа хотят использовать разную логику сравнения. В качестве надуманного примера, объекты человека, для одного объекта я могу захотеть сравнить по полному имени, другой может захотеть сравнить по фамилии. - person liang; 11.12.2013
comment
Это 50% правильного ответа, остальные 50% заключаются в том, что в разных ситуациях объект может быть равен другому, но не в других, например, у вас есть класс человека, если человек A и человек B оба являются мужчинами, тогда они могут быть классифицированы как равный в поиске пола в этом приведении, перекрывающем объект. Равные были бы очень плохой идеей. - person MikeT; 08.06.2015
comment
не могли бы вы обновить ссылку на github? кажется сломанным! - person kuldeep; 01.09.2017
comment
@k2ibegin У меня больше нет этого файла, но самый актуальный код находится в сообщении. - person BrunoLM; 04.09.2017
comment
Итак, чтобы любая операция использовала IEqualityComparer, мне нужно предоставить его в качестве параметра, что означает, что методы, которые не принимают IEqualityComparer, нуждаются в Equals/GetHashCode. - person ed22; 25.04.2019

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

person Gabriele Giuseppini    schedule 16.06.2013

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

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

person Kyle    schedule 16.06.2013

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

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

person supercat    schedule 30.06.2013