Equality of records with lists in C# 9.0

9.9k views Asked by At

Today, I was experimenting with the new features in C# 9.0, and I noticed that when I use a record that contains a List<Point2d>, two records with the same values in their List<Point2d> are not considered equal. Point2d is also a record type in this case.

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 equality methods for the record and redefine the Equals() method, then the records are considered equal 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 to solve this problem, or are there other considerations that I need to take into account when working with record data types?

1

There are 1 answers

2
V0ldek On BEST ANSWER

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.