てくメモ

trivial な notes

【C#】基本的なハッシュ値の組み合わせ

C#での基本的なハッシュ値の組み合わせ手段については、

  • 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