Equality of records with lists in C# 9.0

Viewed 3513

I was experimenting with the C# 9.0 features today and noticed that when a record has a List<Point2d>, then two records with the same items in the list are not equal anymore. Point2d is also a record here.

public sealed record Point2d
{
    public Point2d(double x, double y)
    {
        X = x;
        Y = y;
    }

    public double X { get; }
    public double Y { get; }
}

public sealed record Polyline2d
{
    private readonly List<Point2d> _corners;

    public Polyline2d(IEnumerable<Point2d> corners)
    {
        _corners = new List<Point2d>(corners);
    }
}

        Polyline2d a = new (new Point2d[] { new(0, 0), new(1, 1), new(2, 2)});
        Polyline2d b = new (new Point2d[] { new(0, 0), new(1, 1), new(2, 2) });

        a == b => false

If I manually add the equality methods for the record and redefine the Equals() method then everything is fine again:

    public bool Equals(Polyline2d other)
    {
        return other != null && _corners.SequenceEqual(other._corners);
    }

    public override int GetHashCode()
    {
        return 42;
    }


    a == b => true

Is this the correct approach for addressing this problem, or are there other considerations of record data types that I need to factor in?

1 Answers

The autogenerated equality comparison for records uses the default equality comparer for all fields (the same has been true for value types since always, btw). And the default equality comparer for List<T> is just reference equality. So it is more or less equivalent to:

public bool Equals(Polyline2d other)
{
    return other != null && _corners.Equals(other._corners);
}

Nothing unexpected and your solution is correct - if you want SequenceEqual used you need to define the Equals yourself. Also note that you don't need to redefine GetHashCode, the compiler still generates its own version even if you specify your own Equals (but that autogenerated GetHashCode will still use the List<T>'s default comparer's GetHashCode). That was some bad advice, thanks Jeremy Lakeman for pointing that out. If GetHashCode returns different values for two instances then Equals must return false for them, so you do in fact need to override GetHashCode so that sequence-equal lists do not return different hashcodes, which would be the case with the default implementation.


Disclaimer: the logic in the synthesised Equals is much more complex than this because it actually uses EqualityComparer<List<Point2d>>.Default and takes record inheritance into account, but it's irrelevant in this case.

Related