89

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

これは、null許容の列挙型では(少し)問題になる可能性があります。定義すると

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

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

次のような「より優れた」等式比較器を作成するのは魅力的です。

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;
  }
}

nullしかし、のハッシュコードが必要な理由はあります0か?

編集/追加:

一部の人々は、これがオーバーライドについてであると考えているようですObject.GetHashCode()。実際にはそうではありません。(ただし、.NETの作成者は、関連GetHashCode()するNullable<>構造体でオーバーライドを行いました。)パラメーターなしのユーザー作成の実装では、ハッシュコードが。であるオブジェクトの状況を処理できません。GetHashCode()null

これは、abstractメソッドのEqualityComparer<T>.GetHashCode(T)実装、またはその他の方法でinterfaceメソッドの実装に関するものIEqualityComparer<T>.GetHashCode(T)です。さて、MSDNへのこれらのリンクを作成している間、これらのメソッドArgumentNullExceptionが唯一の引数が。である場合にこれらのメソッドがスローすることを示していることがわかりますnull。これは確かにMSDNの間違いであるに違いありませんか?.NET独自の実装はいずれも例外をスローしません。その場合にスローすると、に追加しようとする試みが事実上中断nullされHashSet<>ます。アイテムHashSet<>を扱うときに特別なことをしない限り(私はそれをテストする必要があります)。null

新しい編集/追加:

今、私はデバッグを試みました。を使用HashSet<>すると、デフォルトの等式比較器を使用して、値Season.Springnull 同じバケットで終了することを確認できます。m_bucketsこれは、プライベート配列メンバーとを非常に注意深く調べることで判断できますm_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;
  }
}
4

9 に答える 9

25

nullに対して返されるハッシュコードが型に対して一貫している限り、問題はありません。ハッシュコードの唯一の要件は、等しいと見なされる2つのオブジェクトが同じハッシュコードを共有することです。

nullの場合は0または-1を返すことは、いずれかを選択して常に返す限り、機能します。明らかに、null以外のハッシュコードは、nullに使用する値を返さないようにする必要があります。

同様の質問:

nullフィールドのGetHashCode?

オブジェクトの識別子がnullの場合、GetHashCodeは何を返す必要がありますか?

このMSDNエントリの「備考」では、ハッシュコードについて詳しく説明しています。痛烈なことに、このドキュメントでは、コミュニティコンテンツでさえも、 null値についての報道や議論まったく提供されていません。

列挙型の問題に対処するには、ハッシュコードを再実装してゼロ以外を返すか、nullに相当するデフォルトの「不明な」列挙型エントリを追加するか、単にnull許容の列挙型を使用しないでください。

ちなみに、面白い発見。

これに関して私が一般的に見ているもう1つの問題は、ハッシュコードが4バイト以上の型を表すことができないことです。たとえば、intのハッシュコードは単なるintであるため、intの全範囲を使用します。その範囲のどの値をnullに選択しますか?選択したものはすべて、値のハッシュコード自体と衝突します。

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

于 2012-05-23T15:50:15.740 に答える
6

ゼロである必要はありません。必要に応じて42にすることができます。

重要なのは、プログラムの実行中の一貫性です。

null内部的にはゼロとして表されることが多いため、これは最も明白な表現です。つまり、デバッグ中にハッシュコードがゼロの場合、「うーん、これはnull参照の問題でしたか?」と考えるように促される可能性があります。

0xDEADBEEFのような数字を使用すると、誰かがあなたが魔法の数字を使用していると言う可能性があることに注意してください...そしてあなたはそうなるでしょう。(ゼロもマジックナンバーであると言うことができます、そしてあなたは一種の正しいでしょう...それが規則のいくらかの例外であるほど広く使われていることを除いて。)

于 2012-05-23T16:15:43.143 に答える
6

ハッシュコードは、同等性を判断するための最初のステップとしてのみ使用され、2つのオブジェクトが同等であるかどうかの事実上の判断として使用されることはありません。

2つのオブジェクトのハッシュコードが等しくない場合、それらは等しくないものとして扱われます(これは、誤った実装が正しいと想定しているためです。つまり、2番目に推測することはありません)。それらが同じハッシュコードを持っている場合は、実際に等しいかどうかをチェックする必要があります。この場合、nullと列挙型の値は失敗します。

結果として、ゼロを使用することは、一般的な場合の他の値と同じくらい良いです。

確かに、列挙型のように、このゼロが実際の値のハッシュコードと共有される状況があります。問題は、あなたにとって、追加の比較のごくわずかなオーバーヘッドが問題を引き起こすかどうかです。

もしそうなら、あなたの特定のタイプのnullableの場合のためにあなた自身の比較器を定義し、null値が常に同じであるハッシュコード(もちろん!)基礎となるものによって生成できない値を生成することを確認してくださいタイプ独自のハッシュコードアルゴリズム。あなた自身のタイプの場合、これは実行可能です。他の人のために-幸運:)

于 2012-05-24T09:02:21.320 に答える
4

良い質問。

私はこれをコーディングしようとしました:

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

次のように実行します。

Season? v = null;
Console.WriteLine(v);

戻りますnull

私がそうするなら、代わりに普通

Season? v = Season.Spring;
Console.WriteLine((int)v);

0期待どおりに、またはにキャストしない場合は単純なSpringを返しintます。

したがって、次のことを行う場合:

Season? v = Season.Spring;  
Season? vnull = null;   
if(vnull == v) // never TRUE

編集

MSDNから

2つのオブジェクトが等しいと比較される場合、各オブジェクトのGetHashCodeメソッドは同じ値を返す必要があります。ただし、2つのオブジェクトが同等と比較されない場合、2つのオブジェクトのGetHashCodeメソッドは異なる値を返す必要はありません。

言い換えると、2つのオブジェクトが同じハッシュコードを持っていて、それらが等しいことを意味しない場合、 実際の同等性はEqualsによって決定されます。

再びMSDNから:

オブジェクトのGetHashCodeメソッドは、オブジェクトのEqualsメソッドの戻り値を決定するオブジェクトの状態に変更がない限り、一貫して同じハッシュコードを返す必要があります。これはアプリケーションの現在の実行にのみ当てはまり、アプリケーションを再度実行すると別のハッシュコードが返される可能性があることに注意してください。

于 2012-05-23T15:50:02.907 に答える
4

しかし、nullのハッシュコードが0でなければならない理由はありますか?

それはまったく何でもあったかもしれません。私は0が必ずしも最良の選択ではないことに同意する傾向がありますが、おそらくバグが最も少ないものです。

ハッシュ関数は、絶対に同じ値に対して同じハッシュを返す必要があります。これを行うコンポーネントが存在すると、これが実際にのハッシュの唯一の有効な値になりますnull。hm 、、のようにこれに定数がある場合、をobject.HashOfNull実装する誰かがIEqualityComparerその値を使用することを知っている必要があります。彼らがそれについて考えなければ、彼らが0を使う可能性は他のどの値よりもわずかに高いと私は思います。

少なくともHashSet<>の場合、nullのハッシュを変更することさえできません

前述のように、nullのハッシュが0であるという規則にすでに従っている型が存在するという理由だけで、完全に停止することは完全に不可能だと思います。

于 2012-06-12T13:29:47.737 に答える
2

簡単にするために0です。そのような厳しい要件はありません。ハッシュコーディングの一般的な要件を確認するだけで済みます。

たとえば、2つのオブジェクトが等しい場合、それらのハッシュコードも常に等しくなければならないことを確認する必要があります。したがって、異なるハッシュコードは常に異なるオブジェクトを表す必要があります(ただし、必ずしもその逆ではありません。2つの異なるオブジェクトが同じハッシュコードを持っている場合がありますが、これが頻繁に発生する場合、これは高品質のハッシュ関数ではありません。良好な衝突耐性)。

もちろん、私は自分の答えを数学的性質の要件に限定しました。.NET固有の技術的条件もあり、ここで読むことができます。null値の0はそれらの中にありません。

于 2012-05-23T15:54:09.157 に答える
1

したがって、これは列挙値を使用することで回避できます(ただし、が不明Unknownであるのは少し奇妙に思えます)。Seasonしたがって、このようなものはこの問題を否定します:

public enum Season
{
   Unknown = 0,
   Spring,
   Summer,
   Autumn,
   Winter
}

Season some_season = Season.Unknown;
int code = some_season.GetHashCode(); // 0
some_season = Season.Autumn;
code = some_season.GetHashCode(); // 3

そうすれば、季節ごとに一意のハッシュコード値が得られます。

于 2012-05-23T16:06:09.327 に答える
1

個人的には、null許容値を使用するのは少し厄介で、できる限りそれらを避けようとします。あなたの問題は別の理由です。非常に便利な場合もありますが、私の経験則では、値型が2つの異なる世界からのものであるという理由だけで、可能であればnullと値型を混在させないことです。.NET Frameworkでも同じように見えます。多くの値型TryParseは、値を値なしから分離する方法であるメソッドを提供します(null)。

Seasonあなたの特定のケースでは、あなたがあなた自身のタイプを扱うので、問題を取り除くのは簡単です。

(Season?)null私にとっては、一部のフィールドが不要なWebフォームがある場合のように、「季節が指定されていない」ことを意味します。enum私の意見では、少し不格好なものを使用するよりも、それ自体にその特別な「値」を指定する方が良いと思いますNullable<T>。より速く(ボクシングなしで)読みやすく(Season.NotSpecifiedvs null)、ハッシュコードの問題を解決します。

もちろん、int値ドメインを拡張したり、値の1つを特殊なものとして指定したりすることができないなど、他のタイプの場合は、常に可能であるとは限りません。しかし、int?ハッシュコードの衝突では、たとえあったとしても、はるかに小さな問題です。

于 2012-05-29T19:24:19.713 に答える
0
Tuple.Create( (object) null! ).GetHashCode() // 0
Tuple.Create( 0 ).GetHashCode() // 0
Tuple.Create( 1 ).GetHashCode() // 1
Tuple.Create( 2 ).GetHashCode() // 2
于 2020-06-06T18:55:19.350 に答える