Come si implementa GetHashCode per una struttura con due stringhe, quando entrambe le stringhe sono intercambiabili

StackOverflow https://stackoverflow.com/questions/70303

  •  09-06-2019
  •  | 
  •  

Domanda

Ho una struttura in C#:

public struct UserInfo
{
   public string str1
   {
     get;
     set;
   }

   public string str2
   {
     get;
     set;
   }   
}

L'unica regola è quella UserInfo(str1="AA", str2="BB").Equals(UserInfo(str1="BB", str2="AA"))

Come sovrascrivere la funzione GetHashCode per questa struttura?

È stato utile?

Soluzione

MSDN:

Una funzione hash deve avere le seguenti proprietà:

  • Se due oggetti vengono confrontati come uguali, il GetHashCode Il metodo per ciascun oggetto deve restituire lo stesso valore.Tuttavia, se due oggetti non si confrontano come uguali, il GetHashCode i metodi per i due oggetti non devono restituire valori diversi.
  • IL GetHashCode metodo per un oggetto deve restituire costantemente lo stesso codice hash purché non vi sia alcuna modifica allo stato dell'oggetto che determina il valore restituito dell'oggetto Equals metodo.Tieni presente che ciò è vero solo per l'esecuzione corrente di un'applicazione e che un codice hash diverso può essere restituito se l'applicazione viene eseguita nuovamente.
  • Per ottenere le migliori prestazioni, una funzione hash deve generare una distribuzione casuale per tutti gli input.

Tenerlo in considerazione nel modo corretto è:

return str1.GetHashCode() ^ str2.GetHashCode() 

^ può essere sostituita con altra operazione commutativa

Altri suggerimenti

Vedere La risposta di Jon Skeet - operazioni binarie come ^ non sono buoni, spesso genereranno hash in collisione!

public override int GetHashCode()
{
    unchecked
    {
        return (str1 ?? String.Empty).GetHashCode() +
            (str2 ?? String.Empty).GetHashCode();
    }
}

Usare l'operatore '+' potrebbe essere migliore che usare '^', perché anche se vuoi esplicitamente che ('AA', 'BB') e ('BB', 'AA') siano esplicitamente uguali, potresti non volere ( 'AA', 'AA') e ('BB', 'BB') devono essere uguali (o tutte coppie uguali).

La regola "il più velocemente possibile" non è completamente rispettata in questa soluzione perché in caso di valori nulli esegue un "GetHashCode()" sulla stringa vuota anziché restituire immediatamente una costante nota, ma anche senza misurare esplicitamente sono disposto azzardare un'ipotesi che la differenza non sarebbe abbastanza grande da preoccuparsi a meno che non ci si aspettino molti valori nulli.

  1. Come regola generale, un modo semplice per generare un codice hash per una classe è eseguire uno XOR su tutti i campi dati che possono partecipare alla generazione del codice hash (facendo attenzione a verificare la presenza di null come sottolineato da altri).Ciò soddisfa anche il requisito (artificiale?) secondo cui gli hashcode per UserInfo("AA", "BB") e UserInfo("BB", "AA") sono gli stessi.

  2. Se puoi fare ipotesi sull'uso della tua classe, forse puoi migliorare la tua funzione hash.Ad esempio, se è normale che str1 e str2 siano uguali, XOR potrebbe non essere una buona scelta.Ma se str1 e str2 rappresentano, ad esempio, nome e cognome, XOR è probabilmente una buona scelta.

Sebbene questo chiaramente non voglia essere un esempio del mondo reale, potrebbe valere la pena sottolineare che:- Questo è probabilmente un pessimo esempio di utilizzo di una struttura:Una struttura dovrebbe normalmente avere una semantica di valore, il che non sembra essere il caso in questo caso.- Anche l'uso di proprietà con setter per generare un codice hash crea problemi.

Un semplice generale il modo è fare questo:

return string.Format("{0}/{1}", str1, str2).GetHashCode();

A meno che tu non abbia requisiti prestazionali rigorosi, questo è il metodo più semplice che mi viene in mente e utilizzo spesso questo metodo quando ho bisogno di una chiave composita.Gestisce il null casi vanno bene e non causeranno (m) alcuna collisione di hash (in generale).Se ti aspetti "/" nelle tue stringhe, scegli semplicemente un altro separatore che non ti aspetti.

public override int GetHashCode()   
{       
    unchecked      
    {           
        return(str1 != null ? str1.GetHashCode() : 0) ^ (str2 != null ? str2.GetHashCode() : 0);       
    }   
}

Seguendo le linee ReSharper suggerisce:

public int GetHashCode()
{
    unchecked
    {
        int hashCode;

        // String properties
        hashCode = (hashCode * 397) ^ (str1!= null ? str1.GetHashCode() : 0);
        hashCode = (hashCode * 397) ^ (str2!= null ? str1.GetHashCode() : 0);

        // int properties
        hashCode = (hashCode * 397) ^ intProperty;
        return hashCode;
    }
}

397 è un numero primo di dimensione sufficiente da causare l'overflow della variabile risultato e mescolare in qualche modo i bit dell'hash, fornendo una migliore distribuzione dei codici hash.Per il resto non c'è niente di speciale in 397 che lo distingua da altri numeri primi della stessa grandezza.

Ah sì, come ha sottolineato Gary Shutler:

return str1.GetHashCode() + str2.GetHashCode();

Può traboccare.Potresti provare a trasmettere a lungo come suggerito da Artem, oppure potresti racchiudere l'affermazione nella parola chiave non selezionata:

return unchecked(str1.GetHashCode() + str2.GetHashCode());

Prova questo:

(((long)str1.GetHashCode()) + ((long)str2.GetHashCode())).GetHashCode()

Molte possibilità.Per esempio.

return str1.GetHashCode() ^ str1.GetHashCode()

Forse qualcosa come str1.GetHashCode() + str2.GetHashCode()?o (str1.GetHashCode() + str2.GetHashCode()) / 2?In questo modo sarebbe lo stesso indipendentemente dal fatto che str1 e str2 vengano scambiati....

Ordinarli, quindi concatenarli:

return ((str1.CompareTo(str2) < 1) ? str1 + str2 : str2 + str1)
    .GetHashCode();

Il risultato di GetHashCode dovrebbe essere:

  1. Più velocemente possibile.
  2. Il più unico possibile.

Tenendo presente tutto ciò, opterei per qualcosa del genere:

if (str1 == null)
    if (str2 == null)
        return 0;
    else
       return str2.GetHashCode();
else
    if (str2 == null)
        return str1.GetHashCode();
    else
       return ((ulong)str1.GetHashCode() | ((ulong)str2.GetHashCode() << 32)).GetHashCode();

Modificare: Ho dimenticato i nulli.Codice corretto.

Troppo complicato e dimentica i valori nulli, ecc.Questo è usato per cose come il bucket, quindi puoi farla franca con qualcosa del genere

if (null != str1) {
    return str1.GetHashCode();
}
if (null != str2) {
    return str2.GetHashCode();
}
//Not sure what you would put here, some constant value will do
return 0;

Ciò è distorto dal presupposto che str1 non sia probabile che sia comune in una percentuale insolitamente ampia di casi.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top