- ①
HashCode.Combine
を使う - ②
ValueTuple
を使う - ③ 要素に素数を乗算する
とされていると思う。
これに細かな情報を足してみる。
ひとつめ。
②は実質HashCode.Combine
。
例えばValueTuple<T1, T2, T3>.GetHashCode
は次のようになっている。(Source Browser より)
public override int GetHashCode() { return HashCode.Combine(Item1?.GetHashCode() ?? 0, Item2?.GetHashCode() ?? 0, Item3?.GetHashCode() ?? 0); }
なので、以下のような場合は同等になる。
public int GetHashByCombine(int a, int b, long c) { return HashCode.Combine(a, b, c); } public int GetHashByTuple(int a, int b, long c) { return (a, b, c).GetHashCode(); } // Console.WriteLine(GetHashByCombine(7, 42, 10000000)); // Console.WriteLine(GetHashByTuple(7, 42, 10000000)); // ↓ // 1555093467 // 1555093467
ふたつめ。
基本的な複数値のハッシュの組み合わせ方として、record
の自動実装を利用する方法もある。
public record class HashRecord( int A, int B, long C); public readonly record struct HashRecordStruct( int A, int B, long C);
以下のようなEqualityComparer<T>.GetHashCode
を用いたGetHashCode()
が自動実装される。
[CompilerGenerated] public override int GetHashCode() { return ((EqualityComparer<Type>.Default.GetHashCode(EqualityContract) * -1521134295 + EqualityComparer<int>.Default.GetHashCode(A)) * -1521134295 + EqualityComparer<int>.Default.GetHashCode(B)) * -1521134295 + EqualityComparer<string>.Default.GetHashCode(C); } [CompilerGenerated] public override readonly int GetHashCode() { return (EqualityComparer<int>.Default.GetHashCode(A) * -1521134295 + EqualityComparer<int>.Default.GetHashCode(B)) * -1521134295 + EqualityComparer<string>.Default.GetHashCode(C); }
もちろんrecord
を定義する必要はあるが、複数の値のハッシュの組み合わせというとき、やりたいことはコレでOKということもあるはず。
上記自動実装コードで一目瞭然だけれど、同一の値を組み合わせても①や②のハッシュ値とは異なるのは一応の注意。
なお、record
クラスは継承可能で、その場合は自動実装されるEqualityContract
プロパティにより異なるハッシュが生成されるようになっている。
参考:レコード型 - C# によるプログラミング入門 | ++C++; // 未確認飛行 C
また、継承クラスでハッシュが異なるのと同様、EqualityContract
プロパティによりクラスと構造体でもハッシュ値が異なる。
var hc = new HashRecord(7, 42, 10000000); var hs = new HashRecordStruct(7, 42, 10000000); Console.WriteLine(hc.GetHashCode()); Console.WriteLine(hs.GetHashCode()); // -83776202 // -1183034575