2016-09-25 50 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