2016-09-25 20 views
4

コードは、このようなものに見えたところ、私はオンライン場所のカップルに遭遇しました:GetHashCode()evil内でF#のハッシュ関数を使用していますか?

[<CustomEquality;NoComparison>] 
type Test = 
    | Foo 
    | Bar 
    override x.Equals y = 
     match y with 
     | :? Test as y' -> 
      match y' with 
      | Foo -> false 
      | Bar -> true // silly, I know, but not the question here 
     | _ -> failwith "error" // don't do this at home 

    override x.GetHashCode() = hash x 

をしかし、私はFSIで上記を実行すると、私はTestのインスタンスにhash fooを呼び出すとき、またはIのいずれかのとき、プロンプトは返しませんfoo.GetHashCode()に直接電話してください。

let foo = Test.Foo;; 
hash foo;; // no returning to the console until Ctrl-break 
foo.GetHashCode();; // no return 

私はそれ容易に証明できなかったが、それは上記のコードを意味し、オブジェクト上のhash x通話GetHashCode()は、危険であることを示唆しています。それともちょうどFSIの演奏ですか?

私は上記のようなコードは "カスタム等価を実装してくださいが、デフォルトとしてハッシュ関数を残してください"という意味だと思いました。

私はこのパターンを別の方法で実装しましたが、hashがちょうどGetHashCode()を呼び出し、永遠のループにつながると仮定して正しいかどうか疑問に思っています。余談として


、FSI内部の平等を使用することは、比較の前に GetHashCode()を呼び出さないか、またはそれが何か他のものをすることを示唆し、すぐに戻ります。 更新:これは上記の例のように意味があります。x.EqualsGetHashCode()を呼び出さず、等価演算子はGetHashCode()ではなくEqualsを呼び出します。 GetHashCode()メソッドがオーバーライドされた場合

答えて

5

hashの機能が単にGetHashCodeのラッパーであるほど単純ではありませんが、実装を使用するのは確実に安全ではないことを心からお伝えできます。override x.GetHashCode() = hash x

あなたを介してhash機能をトレースする場合は、hereを終わる:

let rec GenericHashParamObj (iec : System.Collections.IEqualityComparer) (x: obj) : int = 
    match x with 
    | null -> 0 
    | (:? System.Array as a) -> 
     match a with 
     | :? (obj[]) as oa -> GenericHashObjArray iec oa 
     | :? (byte[]) as ba -> GenericHashByteArray ba 
     | :? (int[]) as ba -> GenericHashInt32Array ba 
     | :? (int64[]) as ba -> GenericHashInt64Array ba 
     | _ -> GenericHashArbArray iec a 
    | :? IStructuralEquatable as a ->  
     a.GetHashCode(iec) 
    | _ -> 
     x.GetHashCode() 

あなたはワイルドカードケースは、したがって、それは無限再帰に自分自身を見つけることは非常に可能です、x.GetHashCode()を呼び出すことがわかります。

GetHashCode()の実装内でhashを使用することができる唯一のケースは、オブジェクトのメンバーの一部を手動でハッシュしてハッシュコードを生成する場合です。

Don Syme's WebLogにこのようにGetHashCode()の中にhashを使用した(非常に古い)例があります。


ところで、あなたが投稿したコードについては、それだけでは安全ではありません。

object.Equalsのオーバーライドでは、例外をスローしないでください。型が一致しない場合はfalseを返します。これは、System.Objectに明確に記載されています。

イコールの実装は例外をスローしてはなりません。 は常に値を返す必要があります。たとえば、objがnullの場合、Equalsメソッド はArgumentNullExceptionをスローする代わりにfalseを返す必要があります。

Source

+0

_ "オブジェクトのメンバの一部を手動でハッシュしているときでしょうか" _、はい、これは実際には 'string *( 'T - >' U ')に似た、私は文字列に 'hash s'を呼び出しました(無限の再帰はありません)。しかし、私はそれらのスレッドをオンラインで見たとき、私は、ちょっと、それを試してみました...この質問につながる。 – Abel

+0

ソースコードへのポインタをありがとう。そして例外に関するあなたのコメントについて:あなたは正しい、悪い例のコードです...私はだまされていました。 – Abel

+0

@Abelええ、あなたはおそらくこれを知っていると思っていましたが、この質問/回答を見る他の人々のためにそこに置く価値があると思っていました。 – TheInnerLight

5

、次いでhash operatorはそれを使用する:

=に従って等しいアイテムの同じハッシュ値を返すように設計された、汎用ハッシュ関数[hash演算子です]オペレーター。デフォルトでは、F#ユニオン、レコードおよびタプル型の構造化ハッシュが使用され、型の完全な内容がハッシュされます。関数の正確な動作は、型ごとにSystem.Object.GetHashCodeを実装することにより、型ごとに調整することができます。

はい、これは悪い考えであり、無限ループにつながると考えられます。

+0

_「機能の正確な動作は、タイプごとにSystem.Object.GetHashCodeを実装することにより、種類別に基づいて調整することができる。」_、私はあまりにも気づいそれは 'GetHashCode()'を呼び出すとは言いません。つまり、x.GetHashCode()= x <0、次にelse else hash x '(つまり、ゼロ以下のものが等しいとみなされる場合)と書くことは全く合理的です。 – Abel

+0

実際、私は 'hash'が' base.GetHashCode() 'を代わりに呼び出すことを期待していました。無限再帰につながるわけではありません。 – Abel

+0

@Abelそれは 'GetHashCode'をオーバーライドすることによって' hash'関数がどのように動作するかを変更することができると言います。あなたはその文を解釈しているように思えます。「ハッシュ関数を使って、GetHashCodeをオーバーライドすることができます。これは、それが言うところの_opposite_です。 – JLRishe

関連する問題