2010-05-27 38 views
28

Ho bisogno di sapere il modo migliore per confrontare due oggetti e scoprire se ci sono uguali. Sto sovrascrivendo sia GethashCode che Equals. Quindi una classe base si presenta come:C# Come trovare se due oggetti sono uguali

public class Test 
{ 
    public int Value { get; set; } 
    public string String1 { get; set; } 
    public string String2 { get; set; } 

    public override int GetHashCode() 
    { 
     return Value^String1.GetHashCode()^String2.GetHashCode(); 
    } 

    public override bool Equals(object obj) 
    { 
     return GetHashCode() == obj.GetHashCode(); 
    } 
} 

Così, per scopi di test ho creato due oggetti:

Test t = new Test() 
{ 
    Value = 1, 
    String1 ="One", 
    String2 = "One" 
}; 

Test t2 = new Test() 
{ 
    Value = 1, 
    String1 = "Two", 
    String2 = "Two" 
}; 

bool areEqual = t.Equals(t2); 

a testare questa areEqual ritorna vero evento se entrambi gli oggetti sono diversi. Mi rendo conto che ciò è dovuto al fatto che String1 e String2 hanno lo stesso valore in ogni oggetto e quindi si annullano a vicenda quando si esegue l'hashing.

C'è un modo migliore di utilizzare l'oggetto hashing che risolverà il problema?

risposta

37

Il tuo attuale metodo di uguaglianza è rotto - ci sono più valori di possibili codici hash. È del tutto ragionevole (e atteso) che di tanto in tanto si disponga di valori non uguali ma che diano lo stesso hash. Uguale dovrebbe controllare i valori effettivi:

public override bool Equals(object obj) 
{ 
    Test test = obj as Test; 
    if (obj == null) 
    { 
     return false; 
    } 
    return Value == test.Value && 
     String1 == test.String1 && 
     String2 == test.String2; 
} 

Poche cose da notare:

  • Il tuo modo di generare il codice hash darà lo stesso valore per qualsiasi fisso Value se String1 e String2 sono il stesso; esploderà anche se String1 o String2 è nullo. Questo è un aspetto sfortunato dell'uso di XOR per l'hashing. Io preferisco qualcosa di simile:

    // Put this extension method in a utility class somewhere 
    public static int SafeGetHashCode<T>(this T value) where T : class 
    { 
        return value == null ? 0 : value.GetHashCode(); 
    } 
    
    // and this in your actual class 
    public override int GetHashCode() 
    { 
        int hash = 19; 
        hash = hash * 31 + Value; 
        hash = hash * 31 + String1.SafeGetHashCode(); 
        hash = hash * 31 + String2.SafeGetHashCode(); 
        return hash; 
    } 
    
  • In generale, l'uguaglianza diventa difficile quando l'ereditarietà viene coinvolto. Puoi prendere in considerazione di sigillare la tua classe.

  • Si consiglia inoltre di implementare IEquatable<Test>

+2

Re 'SafeGetHashCode' - solo per dire che' EqualityComparer .GetHashCode' farà qualcosa di molto simile - il metodo di estensione è abbastanza ordinata, anche se, p –

+0

La tua risposta è veramente buono, mentre la prima parola è sbagliata: "No" è la risposta sbagliata alla sua domanda: "C'è un modo migliore ..." Inoltre la classe di Simons Test non ha una classe base né implementa un'interfaccia. quindi la parola chiave override è davvero sbagliata – OlimilOops

+1

@Oops: risolverà il "No" ... ma la classe Test * ha * una classe base - la classe base implicita di System.Object. Questi metodi * stanno * sovrascrivendo le implementazioni degli oggetti. –

16

tuo Equals non è corretto - che dovrebbe definire cosa significa per due cose siano uguali - e avere lo stesso hash-codice non uguaglianza media (tuttavia, un diverso codice hash fa significa non uguaglianza). Se "uguaglianza" significa "entrambe le stringhe sono uguali a coppie", quindi test.

Re un hash migliore; xor è noto per questo, dato che è banale ottenere 0 da xor un valore con se stesso. Un approccio migliore potrebbe essere qualcosa di simile:

int i = 0x65407627; 
i = (i * -1521134295) + Value.GetHashCode(); 
i = (i * -1521134295) + (String1 == null ? 0 : String1.GetHashCode()); 
i = (i * -1521134295) + (String2 == null ? 0 : String2.GetHashCode()); 
return i; 
+0

(aggiornato, prendendo in prestito il seme e il moltiplicatore utilizzati dal compilatore C# per i tipi anonimi) –

+0

Perché hai scelto quei valori per generare il codice hash? –

+0

@Carlos: vedi il commento precedente di Marc :) –

2

Per due oggetti, l'uguaglianza oggetto implica hash uguaglianza codice, tuttavia , uguaglianza codice hash non implica l'uguaglianza oggetto. Da Object.GetHashCode su MSDN:

Una funzione hash deve avere le seguenti proprietà :

Se due oggetti risultano uguali, il metodo GetHashCode per ogni oggetto deve restituire lo stesso valore.Tuttavia, se due oggetti non vengono confrontati come uguale a , i due metodi GetHashCode per l'oggetto non devono restituire valori diversi .

In altre parole, il tuo Equals è stato scritto male. Dovrebbe essere qualcosa di simile:

public override bool Equals(object obj) 
{ 
    Test other = obj as Test; 
    if (other == null) 
     return false; 

    return (Value == other.Value) 
     && (String1 == other.String1) 
     && (String2 == other.String2); 
} 

GetHashCode è un bene per le collezioni (come Dictionary<K, V>) per determinare rapidamente l'uguaglianza approssimativa. Equals serve per confrontare se due oggetti sono realmente uguali.

3

In aggiunta a tutte le grandi risposte che vi consiglio di leggere questo article da Alois Kraus

+0

Sì, mi piace l'operatore == ''override'? Penso che funzioni bene per confrontare i valori effettivi del mio oggetto 2, per vedere se hanno lo stesso valore da fonti diverse, senza dover chiamare metodi o estensioni. Penso che questa sia una risposta ottima, ma non sono sicuro di quale principio di programmazione cadrà? Potrebbe confondere alcuni sviluppatori? – ppumkin

0

sarebbe non una funzione è sempre uguale testare solo contro lo stesso tipo, non dovrebbe essere:

//override 
    public bool Equals(Test other)//(object obj) 
    { 
     //return GetHashCode() == obj.GetHashCode(); 
     return (Value == other.Value) && 
       (String1 == other.String1) && 
       (String2 == other.String2); 
    } 

saluti Oops

+0

Si * dovrebbe * sovrascrivere il metodo da Oggetto. Potresti * anche * implementare "IEquatable ", che è quando specifichi questa firma. –

+0

hmm non lo so, è un problema della beta VS2010? Se uso override qui, VS dice: 'Test.Equals (Test)': nessun metodo adatto trovato per sovrascrivere – OlimilOops

2

Bene !!!!! (MSDN)

+0

Formattazione veramente malvagia per essere onesti ... – cyberzed

+0

La copia di più pagine di MSDN non è generalmente utile; è meglio collegare ed evidenziare ogni bit di particolare significato. Ma non l'intera pagina. –

4

semplice

Object.Equals(obj1, obj2); 
+1

Tutto dipende dal fatto che obj1/obj2 siano tipi di riferimento o meno. Vedi http://msdn.microsoft.com/en-us/library/bsc2ak47.aspx –

+0

thx u r right./// –

+0

Questa è in realtà la risposta migliore, è la più breve, più chiara e corretta al 100%. –

1

Si potrebbe puntate i due oggetti a JSON, quindi confrontare le due stringhe per vedere se sono gli stessi.

Per esempio

JavaSriptSerializer serialiser = new JavaScriptSerializer(); 

string t1String = serialiser.Serialize(t); 
string t2String = serialiser.Serialize(t2); 

if(t1String == t2String) 
    return true; //they are equal 
else 
    return false; 
+2

non funziona sempre. JSON ha i suoi limiti. A seconda del JSON confrontare l'oggetto generale non è una buona idea. – liang

+0

Provato questo e non funziona. Genera falsi negativi a causa del diverso ordinamento delle proprietà nel JSON. – NickG

Problemi correlati