2013-06-16 20 views
11

Kiedy używam słowników, czasami muszę zmienić domyślne znaczenie w celu porównania kluczy. Widzę, że jeśli nadpisuję Equals i GetHashCode na klasie klucza lub utworzyć nową klasę, która implementuje IEqualityComparer, mam taki sam wynik. Jaka jest więc różnica między używaniem IEqualityComparer a Equals/GethashCode Override? dwa przykłady:Jaka jest różnica między używaniem IEqualityComparer a korektą 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(); 
     } 
    } 
} 

}

drugi:

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

}

Oba przykłady mają ten sam rezultat.

Z góry dziękuję.

+3

Możliwy duplikat/podobny: http://stackoverflow.com/questions/7751170/why-we-need-thealitycomparer-iequalitycomparert-interface – Clint

+4

Ponieważ istnieje więcej niż jeden sposób porównywania niektórych obiektów. – Pragmateek

Odpowiedz

9

Po zastąpieniu Equals i GetHashCode zmieniasz sposób, w jaki obiekt określi, czy jest równy drugiemu. Uwaga: jeśli porównasz obiekty za pomocą operatora ==, nie będzie to miało takiego samego zachowania, jak Equals, chyba że zastąpisz także operatora.

Czy zmieniłeś zachowanie jednej klasy, a jeśli potrzebujesz tej samej logiki dla innych zajęć? Jeśli potrzebujesz "ogólnego porównania". Właśnie dlatego masz IEqualityComparer.

Spójrz na ten przykład:

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

mam dwie różne klasy, obie mogą korzystać z tego samego comparer.

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 

Zauważ, że nie miałem zastąpić Equals, GetHashCode w żadnym z tych klas. Mogę użyć tego porównywalnika w dowolnym obiekcie, który implementuje ICustom, bez konieczności przepisywania logiki porównania. Mogę również utworzyć IEqualityComparer dla "klasy nadrzędnej" i użyć na klasach dziedziczących. Mogę mieć porównań, które będą się zachowywać w inny sposób, mogę dokonać porównania Value zamiast Key.

Tak więc IEqualityComparer pozwala na większą elastyczność i można wdrożyć ogólne rozwiązania.

+8

Mówiąc najprościej, IEqualityComparer przekazuje logiki porównania, podczas gdy zastępuje je Equals/GetHashCode, internalizuje je - Ta sama zasada/różnica dla IComparable (internalize) i IComparer (Externalize). – h9uest

+1

@ h9uest: dobrze powiedziane. Zastanawiam się, czy IEqualityComparer może być również internalizowane. Equals/GetHashCode nie tylko internalizuje logikę porównania, ale także globalizuje je. Mogą zaistnieć przypadki, w których chciałbym przeprowadzić wewnętrzne porównanie (nie używając kolekcji) tylko raz. – liang

+0

@liang Obawiam się, że IEqualityComparer został zaprojektowany do eksternalizacji porównania. Zazwyczaj pisałem MyCustomeComparer, który implementuje IEqualityComparer i przekazuje obiekt MyCustomeComparer do dowolnych obiektów, które go potrzebują - jestem pewien, że zdajesz sobie z tego sprawę. – h9uest

1

Zasadniczo to samo w tym celu z jedną subtelną różnicą. W pierwszym przykładzie zastępujesz Equals, używając parametru typu Object, a następnie przesyłasz go do klienta, jednak w drugim przykładzie możesz mieć parametr typu Customer, co oznacza, że ​​nie trzeba go przesyłać.

Oznacza to, że nadrzędne wartości równe umożliwiają porównanie dwóch różnych typów obiektów (które mogą być potrzebne w pewnych okolicznościach), jednak wdrożenie programu IEqualityComparer nie daje tej swobody (która może być również potrzebna w pewnych okolicznościach).

3

Obiekt obiektu Equals() anf GetHashCode() realizuje koncepcję równości nieodłączną dla obiektu. Możesz jednak użyć alternatywnych koncepcji równości - na przykład porównywania równości dla obiektów adresowych, które używają tylko kodu pocztowego, a nie pełnego adresu.

1

Istnieje wiele przypadków, w których można chcieć znaleźć obiekty, używając czegoś innego niż 100% równoważności. Jako prosty przykład, można chcieć mieć słownik, który pasuje w sposób niewrażliwy na wielkość liter. Jednym ze sposobów osiągnięcia tego byłoby przekonwertowanie ciągów znaków do kanonicznej postaci wielkiej litery przed ich zapisaniem w słowniku lub wykonaniem wyszukiwania. Alternatywnym podejściem jest dostarczenie słownika z IEqualityComparer<string>, który wyliczy hash-kody i sprawdzi równość w jakiejś funkcji niezależnej od przypadku. W pewnych okolicznościach konwertowanie łańcuchów do kanonicznej formy i używanie tej formy, gdy tylko jest to możliwe, będzie bardziej wydajne, ale są też inne, w których efektywniejsze jest przechowywanie ciągu w jego oryginalnej postaci. Jedną z funkcji, którą chciałbym .NET, który poprawiłby użyteczność takich słowników, byłby sposób zażądania faktycznego klucza obiektu związanego z danym kluczem (więc jeśli słownik zawierał jako klucz "WowZo", można było wyszukać "wowzo" i uzyskać "WowZo", niestety jedynym sposobem na odzyskanie rzeczywistego obiektu klucza, jeśli TValue nie zawiera nadmiarowego odniesienia do niego, jest wyliczenie całej kolekcji).

Innym scenariuszem, w którym może być przydatne posiadanie alternatywnych sposobów porównywania, gdy obiekt zawiera odniesienie do instancji typu zmiennego, ale nigdy nie wystawi tej instancji na cokolwiek, co może ją zmutować. Ogólnie rzecz biorąc, dwa wystąpienia int[], które przechowują tę samą sekwencję wartości, nie będą wymienne, ponieważ byłoby możliwe, że w przyszłości jedna lub obie z nich mogą zostać zmienione w celu zachowania różnych wartości. Z drugiej strony, jeśli słownik będzie używany do przechowywania i wyszukiwania wartości, z których każda będzie jedyną referencją w dowolnym miejscu wszechświata do instancji int[], i jeśli żadna z instancji nie zostanie zmodyfikowana ani wystawiona na zewnątrz kod, może być użyteczne uznawanie za równe instancje macierzy, które przechowują identyczne sekwencje wartości. Od Array.Equals testów na ścisłą równoważność (równanie odniesienia), konieczne byłoby użycie innych metod testowania tablic dla równoważności.

Powiązane problemy