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

Using F#'s hash function inside GetHashCode() 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 中 运行 以上内容时,当我在 Test 的实例上调用 hash foo 或调用 foo.GetHashCode()直接。

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

我无法轻易证明,但它表明 hash x 在对象上调用了 GetHashCode(),这意味着上面的代码是危险的。还是只是 FSI 在玩?

我认为上面的代码只是表示 "please implement custom equality, but leave the hash function as default"。

我同时以不同的方式实现了这个模式,但我仍然想知道我假设 hash 只是调用 GetHashCode() 是否正确,导致一个永恒的循环。


顺便说一句,立即在 FSI returns 中使用相等性,表明它要么在比较之前不调用 GetHashCode(),要么它做了其他事情。 更新:这是有道理的,因为在上面的示例中 x.Equals 不调用 GetHashCode(),并且相等运算符调用 Equals,而不是 GetHashCode().

如果 GetHashCode() 方法被覆盖,那么 hash operator 将使用:

[The hash operator is a] generic hash function, designed to return equal hash values for items that are equal according to the = operator. By default it will use structural hashing for F# union, record and tuple types, hashing the complete contents of the type. The exact behavior of the function can be adjusted on a type-by-type basis by implementing System.Object.GetHashCode for each type.

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

它并不像 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 的重写绝对不能抛出异常。如果类型不匹配,它们将 return false。 System.Object.

中清楚地记录了这一点

Implementations of Equals must not throw exceptions; they should always return a value. For example, if obj is null, the Equals method should return false instead of throwing an ArgumentNullException.

(Source)