Überprüfen Sie zunächst, ob Sie überhaupt einen Hashcode benötigen. Werden Sie diese Listen in eine Hash-Struktur (z. B. ein Wörterbuch, ein Hashset usw.) einfügen? Wenn nicht, vergessen Sie es.
In der Annahme, dass Sie meinen, dass EnumerableObject bereits überschreibt Equals(object)
(und implementiert daher hoffentlich auch IEquatable<EnumerableObject>
) aus irgendeinem Grund, dann ist dies in der Tat notwendig. Sie wollen ein Gleichgewicht zwischen Geschwindigkeit und Bitverteilung herstellen.
Ein guter Ausgangspunkt ist ein mult+add oder ein shift+xor wie:
public override int GetHashCode()
{
int res = 0x2D2816FE;
foreach(var item in this)
{
res = res * 31 + (item == null ? 0 : item.GetHashCode());
}
return res;
}
(Dies setzt voraus, dass Sie item.Equals() für Ihre Sequenz Gleichheit Vergleich verwenden, wenn Sie eine IEqualityComparer's gleich müssen Sie in seinem Hashcode aufrufen).
Von dort aus können wir optimieren.
Wenn Nullelemente nicht zulässig sind, entfernen Sie die Null-Prüfung (Vorsicht, dies führt dazu, dass der Code ausfällt, wenn er jemals eine Null findet).
Wenn sehr große Listen üblich sind, müssen wir die Anzahl der untersuchten Listen reduzieren, ohne dass es zu vielen Kollisionen kommt. Vergleichen Sie die folgenden unterschiedlichen Implementierungen:
public override int GetHashCode()
{
int res = 0x2D2816FE;
int max = Math.Min(Count, 16);
for(int i = 0, i != max; ++i)
{
var item = this[i];
res = res * 31 + (item == null ? 0 : item.GetHashCode());
}
return res;
}
public override int GetHashCode()
{
int res = 0x2D2816FE;
int min = Math.Max(-1, Count - 16);
for(int i = Count -1, i != min; --i)
{
var item = this[i];
res = res * 31 + (item == null ? 0 : item.GetHashCode());
}
return res;
}
public override int GetHashCode()
{
int res = 0x2D2816FE;
int step = Count / 16 + 1;
for(int i = 0, i < Count; i += step)
{
var item = this[i];
res = res * 31 + (item == null ? 0 : item.GetHashCode());
}
return res;
}
Dadurch wird die Gesamtzahl der untersuchten Elemente begrenzt, was die Ausführung beschleunigt, aber das Risiko einer schlechteren Qualität der Hashes birgt. Welche Methode die beste ist, hängt davon ab, ob Sammlungen mit demselben Anfang oder demselben Ende wahrscheinlicher sind.
Durch Ändern der Zahl 16 oben wird das Gleichgewicht angepasst; eine kleinere Zahl ist schneller, aber eine höhere Zahl bedeutet eine bessere Hash-Qualität und ein geringeres Risiko von Hash-Kollisionen.
Edit: Und jetzt können Sie meine Implementierung von SpookyHash v. 2 :
public override int GetHashCode()
{
var hasher = new SpookyHash();//use methods with seeds if you need to prevent HashDos
foreach(var item in this)
hasher.Update(item.GetHashCode());//or relevant feeds of item, etc.
return hasher.Final().GetHashCode();
}
Dies führt zu einer viel besseren Verteilung als mult+add oder shift+xor und ist zudem besonders schnell (insbesondere bei 64-Bit-Prozessen, da der Algorithmus dafür optimiert ist, obwohl er auch bei 32-Bit gut funktioniert).