Verwendung von System.HashCode
Wenn Sie .NET Standard 2.1 oder höher verwenden, können Sie die System.HashCode Struktur. Bei früheren Frameworks ist sie über die Microsoft.Bcl.HashCode
Paket. Es gibt zwei Methoden, es zu verwenden:
HashCode.Kombinieren
El Combine
kann zur Erstellung eines Hash-Codes mit bis zu acht Objekten verwendet werden.
public override int GetHashCode() => HashCode.Combine(this.object1, this.object2);
HashCode.Add
El Add
Methode hilft Ihnen bei der Bearbeitung von Sammlungen:
public override int GetHashCode()
{
var hashCode = new HashCode();
hashCode.Add(this.object1);
foreach (var item in this.collection)
{
hashCode.Add(item);
}
return hashCode.ToHashCode();
}
GetHashCode leicht gemacht
Eine Alternative zu System.HashCode
das super einfach zu bedienen und trotzdem schnell ist. Sie können den vollständigen Blogbeitrag lesen ' GetHashCode leicht gemacht ' für weitere Einzelheiten und Kommentare.
Beispiel für die Verwendung
public class SuperHero
{
public int Age { get; set; }
public string Name { get; set; }
public List<string> Powers { get; set; }
public override int GetHashCode() =>
HashCode.Of(this.Name).And(this.Age).AndEach(this.Powers);
}
Umsetzung
public struct HashCode : IEquatable<HashCode>
{
private const int EmptyCollectionPrimeNumber = 19;
private readonly int value;
private HashCode(int value) => this.value = value;
public static implicit operator int(HashCode hashCode) => hashCode.value;
public static bool operator ==(HashCode left, HashCode right) => left.Equals(right);
public static bool operator !=(HashCode left, HashCode right) => !(left == right);
public static HashCode Of<T>(T item) => new HashCode(GetHashCode(item));
public static HashCode OfEach<T>(IEnumerable<T> items) =>
items == null ? new HashCode(0) : new HashCode(GetHashCode(items, 0));
public HashCode And<T>(T item) =>
new HashCode(CombineHashCodes(this.value, GetHashCode(item)));
public HashCode AndEach<T>(IEnumerable<T> items)
{
if (items == null)
{
return new HashCode(this.value);
}
return new HashCode(GetHashCode(items, this.value));
}
public bool Equals(HashCode other) => this.value.Equals(other.value);
public override bool Equals(object obj)
{
if (obj is HashCode)
{
return this.Equals((HashCode)obj);
}
return false;
}
public override int GetHashCode() => this.value.GetHashCode();
private static int CombineHashCodes(int h1, int h2)
{
unchecked
{
// Code copied from System.Tuple a good way to combine hashes.
return ((h1 << 5) + h1) ^ h2;
}
}
private static int GetHashCode<T>(T item) => item?.GetHashCode() ?? 0;
private static int GetHashCode<T>(IEnumerable<T> items, int startHashCode)
{
var temp = startHashCode;
var enumerator = items.GetEnumerator();
if (enumerator.MoveNext())
{
temp = CombineHashCodes(temp, GetHashCode(enumerator.Current));
while (enumerator.MoveNext())
{
temp = CombineHashCodes(temp, GetHashCode(enumerator.Current));
}
}
else
{
temp = CombineHashCodes(temp, EmptyCollectionPrimeNumber);
}
return temp;
}
}
Was macht einen guten Algorithmus aus?
Leistung
Der Algorithmus, der einen Hash-Code berechnet, muss schnell sein. Ein einfacher Algorithmus ist in der Regel der schnellere. Ein Algorithmus, der keinen zusätzlichen Speicher zuweist, verringert auch den Bedarf an Garbage Collection, was wiederum die Leistung verbessert.
Speziell in C#-Hash-Funktionen verwenden Sie oft die unchecked
Schlüsselwort, das die Überlaufprüfung beendet, um die Leistung zu verbessern.
Deterministisch
Der Hashing-Algorithmus muss sein deterministisch d.h. bei gleichem Input muss er immer den gleichen Output produzieren.
Kollisionen vermindern
Der Algorithmus, der einen Hash-Code berechnet, muss die Hash-Kollisionen auf ein Minimum reduziert werden. Eine Hash-Kollision ist eine Situation, die eintritt, wenn zwei Aufrufe von GetHashCode
bei zwei verschiedenen Objekten identische Hash-Codes ergeben. Beachten Sie, dass Kollisionen zulässig sind (manche glauben fälschlicherweise, dass sie es nicht sind), aber sie sollten auf ein Minimum beschränkt werden.
Viele Hash-Funktionen enthalten magische Zahlen wie 17
o 23
. Diese sind besonders Primzahlen die aufgrund ihrer mathematischen Eigenschaften dazu beitragen, Hash-Kollisionen im Vergleich zur Verwendung von Nicht-Primzahlen zu verringern.
Gleichmäßigkeit der Hashwerte
Eine gute Hash-Funktion sollte die erwarteten Eingaben so gleichmäßig wie möglich auf ihren Ausgabebereich abbilden, d. h. sie sollte einen breiten Bereich von Hashes auf der Grundlage ihrer Eingaben ausgeben, die gleichmäßig verteilt sind. Sie sollte eine Hash-Uniformität aufweisen.
DoS verhindern
In .NET Core erhalten Sie bei jedem Neustart einer Anwendung unterschiedliche Hash-Codes. Dies ist eine Sicherheitsfunktion, um Denial-of-Service-Angriffe (DoS) zu verhindern. Bei .NET Framework können Sie devrait aktivieren Sie diese Funktion, indem Sie die folgende App.config-Datei hinzufügen:
<?xml version ="1.0"?>
<configuration>
<runtime>
<UseRandomizedStringHashAlgorithm enabled="1" />
</runtime>
</configuration>
Aufgrund dieser Eigenschaft sollten Hash-Codes niemals außerhalb der Anwendungsdomäne, in der sie erstellt wurden, verwendet werden, sie sollten niemals als Schlüsselfelder in einer Sammlung verwendet werden und sie sollten niemals persistiert werden.
Lesen Sie mehr darüber ici .
Kryptografisch sicher?
Der Algorithmus muss nicht zwangsläufig ein Kryptographische Hash-Funktion . Das bedeutet, dass sie die folgenden Bedingungen nicht erfüllen muss:
- Es ist nicht möglich, eine Nachricht zu erzeugen, die einen bestimmten Hash-Wert ergibt.
- Es ist nicht möglich, zwei verschiedene Nachrichten mit demselben Hash-Wert zu finden.
- Eine kleine Änderung an einer Nachricht sollte den Hash-Wert so stark verändern, dass der neue Hash-Wert mit dem alten Hash-Wert unkorreliert erscheint (Lawineneffekt).