Kas*_*man 5 f# equality discriminated-union
我试图理解这种平等行为。记录相等性测试失败,但记录唯一属性的相等性测试通过。这是一个错误吗?或者有人可以解释这种行为吗?
type TestUnion =
| Case1
| Case2 of (int -> string)
type TestType =
{
Foo : TestUnion
}
open Microsoft.VisualStudio.TestTools.UnitTesting
[<TestClass>]
public Testing() =
let a = { Foo = Case1 }
let b = { Foo = Case1 }
[<TestMethod>]
member __.ThisFails () =
Assert.AreEqual(a, b)
[<TestMethod>]
member __.ThisPasses () =
Assert.AreEqual(a.Foo, b.Foo)
Run Code Online (Sandbox Code Playgroud)
我知道它失败的原因是因为其中一个案例是一个函数。如果我将其更改为简单值,则两个测试都会通过。但对我来说奇怪的是,a)相等性根本失败,因为使用了没有值的简单情况,b)记录相等性失败,而属性相等性通过。
注意:当还存在其他简单属性时,记录相等将失败。IOW,联合类型毒害了整个记录的相等性,即使联合类型属性测试为相等。
方法Assert.AreEqual
想得很巧妙,当然失败了。给定两个对象,此方法要做的第一件事是测试引用相等性:obj.ReferenceEquals( Case1, Case1 )
。这立即生效,因为所有Case1
值都是同一个对象。
现在,如果Assert.AreEqual
的参数不是同一个对象,它将继续调用obj.Equals
。根据您的记录, 的实现Equals
将始终返回 false,因为 F# 编译器没有为其实现相等。为什么?因为某些字段(即TestUnion
)的类型不相等。为什么没有TestUnion
平等呢?因为它至少有一种类型不具有相等性的情况 - 即int -> string
.
如果您更改Case1
为类似的内容Case1 of int
然后尝试Assert.AreEqual( Case1 42, Case1 42 )
,测试将失败。这种情况将会发生,因为 的两个实例化Case1 42
将不再是同一个对象(除非您使用优化进行编译),并且 的Equals
实现TestUnion
将始终返回 false。
如果你真的希望它起作用(并且你真的知道如何比较函数),你总是可以Equals
自己实现:
[<CustomEquality; NoComparison>]
type TestType = { Foo: TestUnion }
with
override this.Equals other = (* whatever *)
override this.GetHashCode() = (* whatever *)
Run Code Online (Sandbox Code Playgroud)
请注意,您必须做很多工作才能完成此任务:您必须添加 aCustomEquality
和NoComparison
(或CustomComparison
)属性,并且还要实现GetHashCode
. 如果你不这样做,编译器会抱怨你的相等性实现不一致。
然而,“正确”的解决方案是始终尽可能多地使用 F# 设施。在这种特定情况下,这意味着使用=
运算符进行比较:
Assert.IsTrue( Case1 = Case1 )
Run Code Online (Sandbox Code Playgroud)
这样,如果您遗漏了某些内容,编译器将始终告诉您:
Assert.IsTrue( a = b )
// The type 'TestType' does not support the 'equality' constraint because blah-blah-blah
Run Code Online (Sandbox Code Playgroud)
F# 编译器通常比底层 .NET CLR 更正确、更一致。