我在网上遇到过几个地方,代码看起来像这样:
[<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()
。
答案 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)