[英]Equality of F# record Types when consumed from C#
我在 F# 库中有以下记录类型:
type Rating = {
Id : string
AverageRating : decimal
ValueRange : int
Label : string }
正如预期的那样,以下测试在 F# 测试库中通过:
[Fact]
let ``ShouldConsiderTwoInstancesOfAClassToBeTheSame`` () =
let a = {Rating.Id = "id"
AverageRating = 4.3m
ValueRange = 10
Label = "label"}
let b = {Rating.Id = "id"
AverageRating = 4.3m
ValueRange = 10
Label = "label"}
a = b |> should equal true
a.Equals b |> should equal true
a <> b |> should equal false
System.Object.ReferenceEquals(a, b) |> should equal false
但是,C# 中的以下测试失败了......前两个断言都失败了,尽管第三个断言通过了:
[Test]
public void ShouldConsiderTwoInstancesOfAClassToBeTheSame()
{
var a = new Rating("id", 4.3m, 10, "label");
var b = new Rating("id", 4.3m, 10, "label");
Assert.True(a == b);
Assert.False(a != b);
Assert.True(a.Equals(b));
Assert.False(ReferenceEquals(a, b));
}
在仅使用运算符从 C# 消费时,有没有办法获得记录类型提供的开箱即用的结构相等性,或者您是否需要调用 Equals() ?
在惯用的 C# 中,引用类型预计不会对结构相等性进行正面测试。 IEquatable
作为契约具有更多的语义意义。
默认情况下,相等意味着引用相等。 F# 在这方面完全不同 - 它使用结构相等进行比较。 如果您查看 F# 为a = b
所做的事情,它会调用
a.Equals(b, GenericEqualityComparer);
但是,对于实现了==
运算符的类型,C# 编译器知道选择运算符的方法。
push.0 //ldloc, ldfld, etc.
push.1
call bool Rating::op_Equality(valuetype Rating, valuetype Rating)
a == b
通常发生的情况是:
push.0
push.1
ceq
ceq比任何方法调用替代方案都快得多,并且是默认的相等比较,它为您提供引用相等。
其他核心 .NET 类型可能没有显式实现==
和!=
,但 JIT 具有特定的实现细节来对这些内部类型(有符号、fp 等)执行相等比较,因此它们仍然正确等同。
如果==
比较在语义上对您很重要,您可以自己实现运算符:
type Rating = {
Id : string
AverageRating : decimal
ValueRange : int
Label : string
} with
static member op_Equality (a: Rating, b: Rating) =
a.Equals b
static member op_Inequality (a: Rating, b: Rating) =
not (a.Equals b)
并且您的测试应该可以再次运行。
为了让==
在 C# 中工作,有问题的类需要实现 == 运算符
bool operator ==(Rating a, Rating b) => ...
如果查看生成的用于评级的 IL 代码,您会看到它实现了IEquatable<T>, IComparable<T>, GetHashCode()
等,但我没有看到运算符实现。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.