在 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
的唯一情况是当您手动散列某些对象的成员以生成散列码时。
中有一个(非常古老的)例子,在 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)
我在网上遇到了几个代码看起来像这样的地方:
[<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
的唯一情况是当您手动散列某些对象的成员以生成散列码时。
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)