2013-06-16 29 views
11

Quando uso i dizionari a volte devo cambiare il significato di Equals di default per confrontare i tasti. Vedo che se eseguo l'override di Equals e GetHashCode sulla classe della chiave o creo una nuova classe che implementa IEqualityComparer, ho lo stesso risultato. Qual è la differenza tra l'utilizzo di IEqualityComparer e Equals/GethashCode Override? Due esempi:Qual è la differenza tra l'utilizzo di IEqualityComparer e 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(); 
     } 
    } 
} 

}

seconda:

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

}

Entrambi gli esempi hanno lo stesso risultato.

Grazie in anticipo.

+3

Possibile dupe/simile: http://stackoverflow.com/questions/7751170/why-we-need-the-iequalitycomparer-iequalitycomparert-interface – Clint

+4

Perché esiste più di un modo per confrontare alcuni oggetti. – Pragmateek

risposta

9

Quando si esegue l'override di Equals e GetHashCode si modifica il modo in cui l'oggetto determinerà se è uguale a un altro. E una nota, se si confrontano gli oggetti usando l'operatore ==, non avrà lo stesso comportamento di Equals a meno che non si sostituisca anche l'operatore.

Facendo questo hai cambiato il comportamento per una singola classe, e se avessi bisogno della stessa logica per le altre classi? Se hai bisogno di un "confronto generico". Questo è il motivo per cui hai IEqualityComparer.

Guardate questo esempio:

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

Ho due classi diverse, sia in grado di utilizzare lo stesso operatore di confronto.

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 

Si noti che non ho dovuto ignorare Equals, GetHashCode in nessuna delle classi. Posso usare questo comparatore in qualsiasi oggetto che implementa ICustom senza dover riscrivere la logica di confronto. Posso anche creare un IEqualityComparer per una "classe genitore" e usarlo su classi che ereditano. Posso avere un confronto che si comporterà in un modo diverso, posso farne uno per confrontare Value invece di Key.

Quindi IEqualityComparer consente una maggiore flessibilità ed è possibile implementare soluzioni generiche.

+8

In poche parole, IEqualityComparer esternalizza le logiche di confronto mentre esegue l'override di Equals/GetHashCode le internizza - Lo stesso principio/differenza per IComparable (internalize) e IComparer (Externalize). – h9uest

+1

@h9uest: ben detto. Mi chiedo se anche IEqualityComparer possa essere interiorizzato. Equals/GetHashCode non solo interiorizza la logica di confronto, ma anche la globalizza. Potrebbero esserci casi in cui vorrei un confronto interno (non usando la raccolta) solo per una volta. – liang

+0

@liang temo che IEqualityComparer sia stato progettato per esternare il confronto. In genere scriverei MyCustomeComparer che implementa IEqualityComparer e passare un oggetto MyCustomeComparer in giro a qualsiasi oggetto che ne abbia bisogno - sono sicuro che sei a conoscenza di questo utilizzo. – h9uest

1

È essenzialmente lo stesso per questo scopo con una sottile differenza. Nel primo esempio, si esegue l'override di Equals utilizzando un parametro di tipo Object e quindi si deve eseguire il cast in Customer, tuttavia nel secondo esempio è possibile avere il parametro di tipo Customer che significa che non è necessario eseguire il cast.

Ciò significa che l'override di Equals consente il confronto tra due oggetti di tipi diversi (che possono essere necessari in determinate circostanze), tuttavia, l'implementazione di IEqualityComparer non offre questa libertà (che potrebbe anche essere necessaria in determinate circostanze).

3

L'oggetto-anf GetHashCode() implementa il concetto di uguaglianza intrinseca all'oggetto. Tuttavia, potresti voler utilizzare concetti alternativi di uguaglianza, ad esempio un comparatore di uguaglianza per gli oggetti indirizzo che utilizza solo il codice ZIP anziché l'indirizzo completo.

1

Ci sono molti casi in cui si potrebbe desiderare di avere uno Dictionary localizzare oggetti usando qualcosa di diverso dal 100% di equivalenza. Come semplice esempio, si potrebbe desiderare di avere un dizionario che corrisponda in modo maiuscole e minuscole. Un modo per ottenere ciò sarebbe convertire le stringhe in una forma maiuscola canonica prima di memorizzarle nel dizionario o eseguire una ricerca. Un approccio alternativo consiste nel fornire al dizionario un IEqualityComparer<string> che calcolerà i codici hash e controllerà l'uguaglianza in una sorta di funzione indipendente dal caso. Ci sono alcune circostanze in cui la conversione di stringhe in forma canonica e l'utilizzo di quella forma quando possibile sarà più efficiente, ma ce ne sono altre in cui è più efficiente archiviare la stringa solo nella sua forma originale. Una caratteristica che desidero .NET e che migliorerebbe l'utilità di tali dizionari sarebbe un mezzo per richiedere l'effettivo oggetto chiave associato a una determinata chiave (quindi se il dizionario conteneva la stringa "WowZo" come chiave, si potrebbe cercare "wowzo" e ottenere "WowZo"; sfortunatamente, l'unico modo per recuperare un oggetto chiave effettivo, se TValue non contiene un riferimento ridondante, è enumerare l'intera raccolta).

Un altro scenario in cui può essere utile disporre di un mezzo di confronto alternativo è quando un oggetto contiene un riferimento a un'istanza di tipo mutabile, ma non esporterà mai tale istanza a qualcosa che potrebbe mutarlo. In generale, due istanze di int[] che contengono la stessa sequenza di valori non saranno intercambiabili, poiché sarebbe possibile che in futuro uno o entrambi potrebbero essere modificati per contenere valori diversi. D'altra parte, se un dizionario verrà utilizzato per conservare e cercare valori int[], ognuno dei quali sarà l'unico riferimento in qualsiasi parte dell'universo a un'istanza di int[] e se nessuna delle istanze verrà modificata o esposta all'esterno codice, può essere utile considerare come istanze di array uguali che contengono sequenze di valori identici. Poiché i test Array.Equals per l'equivalenza rigorosa (uguaglianza di riferimento), sarebbe necessario utilizzare altri metodi per testare gli array per l'equivalenza.