简体   繁体   中英

Distinct() not working as expected, and not calling Equals method, on a List of user defined objects

I have created my own definition of Tuple so that I can have my own definition of the Equals method, in which (1, 3) is equal to (3, 1). The Code is below.

public struct Tuple : IEquatable<object>
{
    public int Item1 { get; set; }
    public int Item2 { get; set; }


    public Tuple(int item1, int item2)
    {
        this.Item1 = item1;
        this.Item2 = item2;
    }

    public override bool Equals(object obj)
    {
        if (obj == null)
        {
            return false;
        }

        if (Object.ReferenceEquals(this, obj))
            return true;

        Tuple other;
        if (obj is Tuple)
        {
            other = (Tuple)obj;
        }
        else
        {
            return false;
        }

        if (this.Item1 == other.Item1 && this.Item2 == other.Item2
          || this.Item1 == other.Item2 && this.Item2 == other.Item1)
        {
            return true;
        }

        return false;
    }

    public override int GetHashCode()
    {
        int hash = 13;
        hash = (hash * 7) + Item1.GetHashCode();
        hash = (hash * 7) + Item2.GetHashCode();
        return hash;
    }

    public static bool operator ==(Tuple left, Tuple right)
    {
        return left.Equals(right);
    }

    public static bool operator !=(Tuple left, Tuple right)
    {
        return !(left == right);
    }
}

But when I run distinct on a List of Tuples, it does not use my definition of Equals. For example, on a vertices variable, like below:

List<Tuple> vertices

I do:

var distinctVertices = vertices.Distinct();

And it does check for distinct values, but it doesn't pass through my Equals definition, thus not working in the case I described above. Any idea on what I might be doing wrong?

Firstly, you probably want to implement IEquatable<Tuple> , not IEquatable<object> - Tuple : IEquatable<Tuple> .


Secondly, equality on a mutable struct is a terrible idea; I strongly suggest you use:

public readonly struct Tuple : IEquatable<Tuple>
{
    public int Item1 { get; }
    public int Item2 { get; }
    public Tuple(int item1, int item2)
    {
        Item1 = item1;
        Item2 = item2;
    }
    ...
}

Thirdly, your Equals and GetHashCode must agree. If the order doesn't matter to Equals , it shouldn't matter to GetHashCode . Consider just:

public override int GetHashCode() => Item1 ^ Item2;

or something else that is not order dependent.


Finally, your Equals can probably be simplfied:

public override bool Equals(object obj) => obj is Tuple other && Equals(other);
public bool Equals(Tuple other)
    => (Item1 == other.Item1 && Item2 == other.Item2)
    || (Item1 == other.Item2 && Item2 == other.Item1);

Take a look at this note in the Distinct documentation :

This method is implemented by using deferred execution. The immediate return value is an object that stores all the information that is required to perform the action. The query represented by this method is not executed until the object is enumerated either by calling its GetEnumerator method directly or by using foreach in Visual C# or For Each in Visual Basic.

This means that, until you enumerate the result, nothing will happen in terms of filtering the sequence.

If you do a foreach or adding a ToList on the result, ie:

var distinctVertices = vertices.Distinct().ToList();

or

foreach(var vertice in distinctVertices)
{
    // optionally do something here
}

Then you'll see your Equals method execute.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM