Reputation: 71
I have the following record type in an F# lib:
type Rating = {
Id : string
AverageRating : decimal
ValueRange : int
Label : string }
And as expected, the following test passes in an F# test lib:
[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
However, the following test in C# fails...both of the first 2 assertions fail, though the third assertion passes fine:
[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));
}
Is there a way to get the structural equality that record types provide out of the box when consuming from C# using just the operators, or do you need to call Equals() ?
Upvotes: 4
Views: 683
Reputation: 12687
In idiomatic C#, reference types are not expected to test positive for structural equality. IEquatable
as a contract makes more semantic sense.
Equality, by default, means equality of reference. F# is quite different in this regard - it uses structural equality for comparisons.
If you look at what F# does for a = b
, it calls
a.Equals(b, GenericEqualityComparer);
However, for a type with an ==
operator implemented, the C# compiler knows to pick the operator's method.
push.0 //ldloc, ldfld, etc.
push.1
call bool Rating::op_Equality(valuetype Rating, valuetype Rating)
What generally happens for a == b
, is:
push.0
push.1
ceq
ceq is much faster than any of the method call alternatives, and is the default equality comparison, which gives you equality of reference.
Other core .NET types may not explicitly implement ==
and !=
, but the JIT has specific implementation details to perform equality comparison for those intrinsic types (signed, fp, etc.), so they still equate correctly.
If an ==
comparison is semantically important to you, you can implement the operators yourself:
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)
And your tests should work again.
Upvotes: 4
Reputation: 22859
In order for ==
to work in C#, the class in question needs to implement the == operator
bool operator ==(Rating a, Rating b) => ...
If you look at the generated IL-Code for Rating, you will see that it implement IEquatable<T>, IComparable<T>, GetHashCode()
, etc. but I have seen no operator implementation.
Upvotes: 1