views:

807

answers:

2

Everytime I write some data class, I usually spend so much time writing the IEquatable implementation.

The last class I wrote was something like:

public class Polygon
{
    public Point[] Vertices { get; set; }
}

Implementing IEquatable was exaustive. Surely C#3.0/LINQ helps a lot, but the vertices can be shifted and/or in the reverse order, and that adds a lot of complexity to the Equals method. After many unit tests, and corresponding implementation, I gave up, and changed my application to accept only triangles, which IEquatable implementation required only 11 unit tests to be fully covered.

There is any tool or technique that helps implementing Equals and GetHashCode?

+4  A: 

I use ReSharper to generate equality members. It will optionally implement IEquatable<T> as well as overriding operators if you want that (which of course you never do, but it's cool anyway).

The implementation of Equals includes an override of Object.Equals(Object), as well as a strongly typed variant (which can avoid unnecessary type checking). The lesser typed version calls the strongly typed one after performing a type check. The strongly typed version performs a reference equality check (Object.ReferenceEquals(Object,Object)) and then compares the values of all fields (well, only those that you tell the generator to include).

As for GetHashCode, a smart factorisation of the field's GetHashCode values are combined (using unchecked to avoid overflow exceptions if you use the compiler's checked option). Each of the field's values (apart from the first one) are multiplied by prime numbers before being combined. You can also specify which fields would never be null, and it'll drop any null checks.

Here's what you get for your Polygon class by pressing ALT+Insert then selecting "Generate Equality Members":

public class Polygon : IEquatable<Polygon>
{
    public Point[] Vertices { get; set; }

    public bool Equals(Polygon other)
    {
        if (ReferenceEquals(null, other)) return false;
        if (ReferenceEquals(this, other)) return true;
        return Equals(other.Vertices, Vertices);
    }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        if (ReferenceEquals(this, obj)) return true;
        if (obj.GetType() != typeof (Polygon)) return false;
        return Equals((Polygon) obj);
    }

    public override int GetHashCode()
    {
        return (Vertices != null ? Vertices.GetHashCode() : 0);
    }
}

Some of the features I talked about above don't apply as there is only one field. Note too that it hasn't checked the contents of the array.

In general though, ReSharper pumps out a lot of excellent code in just a matter of seconds. And that feature is pretty low on my list of things that makes ReSharper such an amazing tool.

Drew Noakes
Isn't magical but it surely helps! Thanks
Jader Dias
That Equals(obj) override doesn't look like excellent code to me. Wouldn't it be more natural to simply say return this.Equals(obj as Polygon);?
mquander
Perhaps simpler code to read, yes. But the performance characteristics would be worse. The JIT will reduce the first line to a simple machine code instruction (cmp with zero) which will be very fast to run. The second line reduces down to another simple machine code instruction (cmp obj pointers) which will also be very fast to run. The third line does a type check (the JIT optimises this down to another pointer check based on the object's type code), and only finally, if the above checks fail is Equals actually called.
Drew Noakes
Furthermore, R# only generates the null check if you tell it that the field can be null (which it could be in the sample code.)
Drew Noakes
+2  A: 

For comparing two arrays of items, I use the SequenceEqual extension method.

As for a generic Equals and GetHashCode, there's a technique based on serialization that might work for you.

Using MemoryStream and BinaryFormatter for reuseable GetHashCode and DeepCopy functions

Cameron MacFarland
Thanks for adding it to my toolset.
Jader Dias
I couldn't find SequenceEqual on 3.5/System.Core.dll, do you know where it is? I am using Red Gate's Reflector to view the source code.
Jader Dias
Great article! Now I realized that writing the Equals implementation is dumb, all you have to do is implement GetHashCode and then use it on Equals.
Jader Dias
Not so dumb as I thinked. Equals relying on GetHashCode have a small chance of returning the wrong answer, because ints have only 32 bits.
Jader Dias
SequenceEqual is in the System.Linq.Enumerable class in System.Core.
Cameron MacFarland