1. ホーム
  2. c#

[解決済み] .NETでは、NULLのハッシュコードは常に0であるべきです。

2023-03-29 21:25:53

質問

のようなコレクションがあるとして System.Collections.Generic.HashSet<> を受け入れる null をセットメンバとすると null のハッシュコードがどうあるべきかを問うことができます。フレームワークでは 0 :

// nullable struct type
int? i = null;
i.GetHashCode();  // gives 0
EqualityComparer<int?>.Default.GetHashCode(i);  // gives 0

// class type
CultureInfo c = null;
EqualityComparer<CultureInfo>.Default.GetHashCode(c);  // gives 0

これは、nullableな列挙型で(少し)問題になることがあります。もし私たちが

enum Season
{
  Spring,
  Summer,
  Autumn,
  Winter,
}

とすると Nullable<Season> (とも呼ばれる)。 Season? ) は5つの値だけを取ることができますが、そのうちの2つ、つまり nullSeason.Spring は同じハッシュコードを持っています。

このようなquot;better"等値比較器を書きたくなりますね。

class NewNullEnumEqComp<T> : EqualityComparer<T?> where T : struct
{
  public override bool Equals(T? x, T? y)
  {
    return Default.Equals(x, y);
  }
  public override int GetHashCode(T? x)
  {
    return x.HasValue ? Default.GetHashCode(x) : -1;
  }
}

のハッシュ・コードは、このような場合にも利用できるのでしょうか? null0 ?

編集・追加です。

一部の人々は、これが Object.GetHashCode() . 実はそうではありません。(但し、.NET の作者は GetHashCode() の中に Nullable<> という構造体で に関連するものですが)。ユーザが書いた実装で、パラメータなしの GetHashCode() は、求めるハッシュコードを持つオブジェクトが null .

これは、抽象的なメソッドである EqualityComparer<T>.GetHashCode(T) を実装するか、あるいはインターフェイスメソッド IEqualityComparer<T>.GetHashCode(T) . さて、MSDN へのこれらのリンクを作成する際に、これらのメソッドが ArgumentNullException が唯一の引数である場合 null . これは確かにMSDNの間違いに違いない?.NET自身の実装のどれもが例外を投げません。その場合に投げることは、事実上 nullHashSet<> . ただし HashSet<> を処理するときに何か特別なことをするのであれば null アイテムを扱うときに何か特別なことをします (テストする必要があります)。

新しい編集/追加です。

今度はデバッグをやってみました。とは HashSet<> で、デフォルトの等比級数比較器では、値 Season.Springnull 意志 は同じバケツで終わります。このことは、プライベート配列のメンバである m_bucketsm_slots . インデックスは設計上、常に1つずつオフセットされていることに注意してください。

しかし、私が上であげたコードはこれを修正するものではありません。結論から言うと HashSet<> は等価比較器に対して、値が null . これは、ソースコードにある HashSet<> :

    // Workaround Comparers that throw ArgumentNullException for GetHashCode(null).
    private int InternalGetHashCode(T item) {
        if (item == null) { 
            return 0;
        } 
        return m_comparer.GetHashCode(item) & Lower31BitMask; 
    }

ということになります。 については少なくとも HashSet<> のハッシュを変更することはできない、ということです。 null . 代わりに、解決策としては、以下のように他のすべての値のハッシュを変更することです。

class NewerNullEnumEqComp<T> : EqualityComparer<T?> where T : struct
{
  public override bool Equals(T? x, T? y)
  {
    return Default.Equals(x, y);
  }
  public override int GetHashCode(T? x)
  {
    return x.HasValue ? 1 + Default.GetHashCode(x) : /* not seen by HashSet: */ 0;
  }
}

どのように解決するのですか?

ヌルに対して返されるハッシュコードが 一貫して である限り、問題ないでしょう。ハッシュ コードの唯一の要件は、等しいと考えられる 2 つのオブジェクトが同じハッシュ コードを共有することです。

nullに対して0または-1を返すことで、1つを選んで常にそれを返す限り、動作します。明らかに、非NULLのハッシュコードは、NULLに使用するいかなる値も返してはいけません。

<ストライク

nullフィールドでGetHashCode?

オブジェクトの識別子がNULLの場合、GetHashCodeは何を返すべきですか?

この記事の内容 MSDN のエントリ は、ハッシュ コードについてより詳細に説明しています。興味深いことに、このドキュメントでは NULL 値について何の説明も議論もありません。 まったく - コミュニティーのコンテンツにおいてさえもです。

enum に関するあなたの問題に対処するために、ゼロ以外を返すようにハッシュコードを再実装するか、null と同等のデフォルトの "unknown" enum エントリを追加するか、単に null 可能な enum を使用しないようにします。

ところで、興味深い発見がありました。

私が一般的に見るもうひとつの問題は、ハッシュコード ができないことです。 がなければ null にできる 4 バイト以上の型を表すことができないことです。 少なくとも1つの衝突 (型サイズが大きくなるともっと)。例えば、intのハッシュコードはintだけなので、intの全範囲を使用します。その範囲の中でnullにどのような値を選ぶのでしょうか?どんなものを選んでも、値のハッシュコードそのものと衝突してしまいます。

衝突それ自体は必ずしも問題ではありませんが、それがあることを知っておく必要があります。ハッシュ コードは、ある状況でのみ使用されます。MSDN のドキュメントに記載されているように、ハッシュ コードは異なるオブジェクトに対して異なる値を返すことは保証されていないので、期待しないほうがよいでしょう。