Каква е разликата между използването на 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();
        }
    }
}

}

И двата примера имат един и същ резултат.

Благодаря предварително.


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% са, че в различни ситуации даден обект може да е равен на друг, но не и в други, например имате клас на човек, ако човек А и човек Б са мъже, тогава много от тях са класифицирани като Equal при търсене по пол в този каст, заменящ обекта. Equal би било много лоша идея - 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() anf 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