1

I'm comparing two objects of type Triangle and apparently they are deemed equal (I implemented my custom GetHaschCode as well as Equal method and operator).

List<Triangle> triangles = ...;
bool same = triangles[0] == triangles[1];
// same is true

However, when I go Distinct() on that list, it keeps all the elements (which sound to me like it's comparing by reference and not by my custom conditions). Is it so and what can I do about it?

int countBefore = triangles.Count();
int countAfter  = triangles.Distinct().Count();
bool same = countBefore == countAfter;
// same is true, again

I'm missing something fairly obvious, am I not?

Konrad Viltersten
  • 36,151
  • 76
  • 250
  • 438
  • 1
    Hmm, can you show a reproducible example? Sounds like incorrect `GetHashCode` implementation. – Ivan Stoev Sep 08 '16 at 19:39
  • 2
    It will not help. If you have **correctly** implemented `Equals` and `GetHashCode` overrides in your `Triangle` class, `Distinct` would work correctly w/o `IEqualtable`. If your `GetHasCode` is incorrect, implementing the interface will not change anything. Just post the class code and we'll tell you what's the problem. See, if you return different hash codes, your `Equals` method will not be called at all. – Ivan Stoev Sep 08 '16 at 19:50
  • @IvanStoev Please post that as reply so I can accept it. You're spot on correct, mate. You can mention something from my comment above too to make the answer even more useful for the future generations. Genius-ishly! – Konrad Viltersten Sep 08 '16 at 20:15
  • @KonradViltersten Thank you mate, but I've seen this many times and I'm pretty sure it's a dupe. For instance, http://stackoverflow.com/questions/371328/why-is-it-important-to-override-gethashcode-when-equals-method-is-overridden does explain all that. – Ivan Stoev Sep 08 '16 at 20:22
  • @IvanStoev It seems so, yes. In fact, I can see I upvote some of the answers there so I **must have seen it**. Yet,didn't made the connection, hehe. – Konrad Viltersten Sep 08 '16 at 20:26

2 Answers2

1

You can do this in one of two ways...

Either as Andrew says implement IEquatable

public class Triangle : IEquatable<Triangle>
{
    bool IEquatable<Triangle>.Equals(Triangle other)
    {
        return Equals(other);
    }

    public override bool Equals(object obj)
    {
        //...
    }

    public override int GetHashCode()
    {
        //...
    }
}

Or you could create another class that implements IEqualityComparer(T) and pass that into the Distinct method call.

public class TriangleComparer : IEqualityComparer<Triangle>
{
    public bool Equals(Triangle x, Triangle y)
    {
        return x.Equals(y);
    }

    public int GetHashCode(Triangle obj)
    {
        return obj.GetHashCode();
    }
}
Scott Perham
  • 2,410
  • 1
  • 10
  • 20
  • I wonder if it's supposed to be *bool IEquatable.Equals(Triangle other)* or just *bool Equals(Triangle other)* like Resharper suggests. – Konrad Viltersten Sep 08 '16 at 19:52
  • It depends if you want to be able to call the method by having to cast to the interface first (explicit implementation) or not (implicit implementation) - It does also depend on the usage, but as it was only going to be used in the Distinct call it probably would make sense to implement it explicitly – Scott Perham Sep 08 '16 at 20:00
0

You need to implement the IEquatable(T) interface in your Triangle class.

From the Enumerable.Distinct() documentation:

The default equality comparer, Default, is used to compare values of the types that implement the IEquatable generic interface. To compare a custom data type, you need to implement this interface and provide your own GetHashCode and Equals methods for the type.

Andrew Jenkins
  • 1,590
  • 13
  • 16
  • I do have *public override bool Equals(object input)* already. Isn't that sufficient? Is it perhaps superfluous to have both that and *public bool Equals(Triangle other)*? – Konrad Viltersten Sep 08 '16 at 19:48
  • I updated my answer. As for why this is the case in C#, check out http://stackoverflow.com/a/2734941/743868 – Andrew Jenkins Sep 08 '16 at 19:55
  • 2
    Well, the documentation is just incorrect. If the class does **not** implement `IEquatable`, the default equality comparer uses `object.Equals` and `object.GetHashCode` overrides. See `ObjectEqualityComparer` in the [source](http://referencesource.microsoft.com/#mscorlib/system/collections/generic/equalitycomparer.cs,542680fa5b2d828d). – Ivan Stoev Sep 08 '16 at 19:56
  • I'm afraid there's something more wrong. I implemented *IEquatable* in my *Triangle* class and added the second *Equal* method as Resharper suggested. Same result, though... – Konrad Viltersten Sep 08 '16 at 19:57
  • Strange, I just tested it and am getting the same result as Ivan and Ed. There is probably something wrong with your Equals or GetHashCode method. – Andrew Jenkins Sep 08 '16 at 20:10
  • Btw, it's better documented here - [EqualityComparer.Default Property](https://msdn.microsoft.com/en-us/library/ms224763(v=vs.110).aspx) - **Remarks** section: *The Default property checks whether type T implements the System.IEquatable interface and, if so, returns an EqualityComparer that uses that implementation. Otherwise, it returns an EqualityComparer that uses the overrides of Object.Equals and Object.GetHashCode provided by T.* – Ivan Stoev Sep 08 '16 at 20:16