2016-09-25 32 views
4

我遇到了幾個地方在線,其中的代碼看起來是這樣的:在GetHashCode()中使用F#的散列函數evil?

[<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,當我要麼調用hash foo上的Test實例或當我提示不回直接致電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.Equals不會調用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中有這樣一個裏面使用hash的(很老的)例子。


順便說一下,這不是唯一不安全的關於您發佈的代碼。

覆蓋object.Equals絕對不能拋出異常。如果類型不匹配,則返回false。這清楚地記錄在System.Object

Equals的實現不能拋出異常;他們應該總是返回一個值,即 。例如,如果obj爲null,則Equals方法 應返回false而不是拋出ArgumentNullException。

Source

+0

_「會在你手動散列一些對象的成員時」_,是的,這實際上是以創建一個可比較的函數類型開始的,類似於string *('T - >'U)',在散列覆蓋我在字符串上調用了'hash s'(所以,在那裏沒有無限遞歸)。但是當我在網上看到這些線索時,我想,嘿,讓我們試試看......引發這個問題。 – Abel

+0

感謝您指向源代碼的指針。關於你對例外的評論:你是對的,不好的例子代碼......我在鬼混。 – Abel

+0

@Abel是的,我想你可能已經知道了這一點,但我認爲值得在其他人看待這個問題/答案的地方出現,因爲這是那些很容易做出的常見小錯誤之一。 – TheInnerLight

5

如果GetHashCode()方法被重寫,則hash operator將使用:

[該hash操作者是]通用散列函數,旨在爲,根據=相等項返回相等的哈希值運營商。默認情況下,它將對F#聯合,記錄和元組類型使用結構化散列,散列整個類型的內容。通過爲每種類型實現System.Object.GetHashCode,可以逐個類型地調整函數的確切行爲。

所以是的,這是一個壞主意,它是有道理的,它會導致無限循環。

+0

_「功能的具體行爲,可以在一個類型的類型的基礎爲每個類型實現System.Object.GetHashCode調整。」 _,我也注意到了,但它並沒有說它會調用'GetHashCode()'。即,如果x <0,則編寫x.GetHashCode()= 0,否則散列x'(即,如果零以下的任何東西被認爲是相等的),完全合理。 – Abel

+0

實際上,我希望'hash'可以調用'base.GetHashCode()',而不會導致無限遞歸。 – Abel

+0

@Abel它說你可以通過重寫'GetHashCode'來改變'hash'函數對於某個類型的行爲方式,如果它實際上沒有調用'GetHashCode()',它的行爲會如何改變?這聽起來像是你將這個句子解釋爲「你可以使用'hash'函數來覆蓋'GetHashCode'」,它就是它所說的_opposite_。 – JLRishe