Sortedset not using custom equals

2019-08-31 03:08发布

My class implements IEquatable and IComparable. It then gets added to a sortedset.

The goal is to have it be sorted by its "Date" property and be equal if both "ID1" and "ID2" are the same.

The implemented methods:

public int CompareTo(MyClass other)
{
    return other.Date.CompareTo(Date);
}

public override int GetHashCode()
{
    unchecked
    {
        var hashCode = ID1;
        hashCode = (hashCode * 397) ^ ID2;
        return hashCode;
    }
}

public bool Equals(MyClass other)
{
    if (ReferenceEquals(null, other)) 
       return false;

    if (ReferenceEquals(this, other)) 
       return true;

    return ID1 == other.ID1
        && ID2 == other.ID2;
}

The resulting sortedset is correctly sorted, but there are still elements that should be equal and therefore not in the set. Using breakpoints it seems neither GetHashCode nor Equals get called.

Any tips on how to solve this?

1条回答
The star\"
2楼-- · 2019-08-31 03:52

SortedSet uses CompareTo for both sorting and equality comparison.

There isn't an in-built ordered collection which will allow you to specify a different method to compare for equality in ordering than to compare for equality in maintaining distinctness. I'm not completely sure why this is, but it may be to do with the asssumptions behind the algorithms used for sorting.

If you want this behaviour, probably the simplest way to do it is wrap the underlying collection in its own class which will check for distinctness before adding new items to the collection.

You also need to be careful that items which are equal for sorting but not equal by your Equals method can both be added to the underlying collection. In your case, where the sorting is done by Date and the equality is done by ID1 and ID2 this might look something like:

public int CompareTo(MyClass other)
{
    var result = other.Date.CompareTo(Date);
    if(result != 0)
        return result;
    result = other.ID1.CompareTo(ID1);
    if(result != 0)
        return result;
    return other.ID2.CompareTo(ID2);
}

This imposes extra ordering if the dates are the same, but I wouldn't expect that to be a problem.

Alternatively, you can "cheat", by forcing items which are equal to compare as equal in sort position. This would probably best be moved to a custom IComparer, because it's not the normal sorting behaviour you want, outside a SortedSet:

public int CompareTo(MyClass other)
{
    if(other.Equals(this))
        return 0;

    var result = other.Date.CompareTo(Date);
    if(result != 0)
        return result;
    result = other.ID1.CompareTo(ID1);
    if(result != 0)
        return result;
    return other.ID2.CompareTo(ID2);
}

This would allow you to avoid having to create your own wrapper class around the collection, and is safer. It is rather hacky, though.

查看更多
登录 后发表回答