Implementing class equality/comparison in F# I'm working on some book material on classes and interfaces in F#. My example is a latitude/longitude class:

    open System

    type LatLon(latitude : float, longitude : float) =

        member __.Latitude = latitude
        member __.Longitude = longitude

        override this.GetHashCode() =
            hash (this.Latitude, this.Longitude)

        override this.Equals(thatObj) =
            match thatObj with
            | :? LatLon as that -&gt; 
                this.Latitude = that.Latitude
                &amp;&amp; this.Longitude = that.Longitude    
            | _ -&gt; false

        interface IEquatable&lt;LatLon&gt; with
            member this.Equals(that : LatLon) =
                this.Equals(that)   

        interface IComparable with
            member this.CompareTo(thatObj) =
                match thatObj with
                | :? LatLon as that -&gt; 
                    compare (this.Latitude, this.Longitude) (that.Latitude, that.Longitude)                    
                | _ -&gt; 
                    raise &lt;| ArgumentException("Can't compare instances of different types")

    let landsEnd = LatLon(50.07, -5.72)
    let johnOGroats = LatLon(58.64, -3.07)
    let landsEnd2 = LatLon(50.07, -5.72)

    let demo() =
        // false
        printfn "%b" (landsEnd = johnOGroats)
        // true
        printfn "%b" (landsEnd = landsEnd2)

        // 50.070000, -5.720000
        // 58.640000, -3.070000
        [ landsEnd; johnOGroats; landsEnd2 ] 
        |&gt; Set.ofList
        |&gt; Seq.iter (fun ll -&gt; printfn "%f, %f" ll.Latitude ll.Longitude)


By the way, I'm well aware that in this case, a record type would give the necessary structural equality out of the box. But the material I want to cover with this example is specifically about classes.

My main concerns are:


The code works the same if I don't implement IEquatable, as it uses the Equals() override. Is it considered desirable to implement IEquatable anyway, and/or does it have advantages I haven't spotted?
What about the (currently very-lightly-documented) op_Equality? Should that be used as well/instead?
The code works fine (for example Set construction, requiring comparison) if I implement IComparable but not IComparable&lt;'T&gt;. Have I missed out on anything useful by not using IComparable&lt;'T&gt;?


Obviously I'm amenable to any other feedback on the quality/style of this code.

Many thanks!
There's a suggestion for [&lt;AllowNullLiteral&gt;]: I think this is a bad idea.

You wrote a class that is a lat/long pair, as a result, it's a standard GeoCoordinate, which is a regular value-type, and should be marked as such. I get that the book calls it a class, but as an F# developer, how many times in your life have you actually had to think about the difference?

As a result, I'd add the structure definition in some manner, either via struct/end, or [&lt;StructAttribute&gt;].

I also recommend implementing the generic IComparable&lt;T&gt;. It's cheap, and can offer more performance to avoid boxing/unboxing should the caller need it. (Reed also covered this.) Some of the newer API's will try to rely on both, preferring the IComparable&lt;T&gt;. You may also want to read this answer to another question, as it talks about a lot of performance intricacies with regard to the standard BCL stuff. (You may want to create an IEqualityComparer&lt;T&gt;, for example.)

Some might argue that the guidelines say to keep the instance size "under 16 bytes", I personally think that's rubbish, as I'm pretty sure that should read "at or under 16 bytes". These are not always law.

The .NET BCL contradicts itself here several times (Size structure, Guid structure, too lazy to find the others), and the Struct Design guidelines don't mention it, only the class vs. struct guidelines.

As a result, I think you're safe in that regard here. The item you're talking about is small (16-bytes), represents a value (physical location that doesn't change), and should not be directly changed. Just as well, the copying performance can often use native SIMD instructions, so it should not be a big deal to copy 16-bytes. (I had a source on this, but I lost it, going to try to dig it up again.)