在GetHashCode()邪恶中使用F#的哈希函数?

时间:2016-09-25 17:25:22

标签: hash f# equality gethashcode

我在网上遇到过几个地方,代码看起来像这样:

[<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()

2 个答案:

答案 0 :(得分:5)

如果覆盖了GetHashCode()方法,则hash operator将使用该方法:

  

[hash运算符是一个]泛型散列函数,旨在根据=运算符为相等的项返回相等的散列值。默认情况下,它将对F#union,record和tuple类型使用结构散列,散列该类型的完整内容。通过为每种类型实现System.Object.GetHashCode,可以在逐个类型的基础上调整函数的确切行为。

所以是的,这是一个坏主意,它会导致无限循环。

答案 1 :(得分: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(),因此很有可能发现自己处于无限递归状态。

我可以在hash的实现中查看您可能想要使用GetHashCode()的唯一情况是手动散列某些对象的成员以生成哈希代码

Don Syme's WebLog中以这种方式在hash内使用GetHashCode()的示例(非常陈旧)。

顺便说一下,对于您发布的代码,这并不是唯一不安全的事情。

object.Equals的覆盖绝对不能抛出异常。如果类型不匹配,则返回false。 System.Object中明确记录了这一点。

  

Equals的实现不得抛出异常;他们应该   总是返回一个值。例如,如果obj为null,则为Equals方法   应该返回false而不是抛出ArgumentNullException。

Source