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?
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:Nothing unexpected and your solution is correct - if you want
SequenceEqualused you need to define theEqualsyourself.Also note that you don't need to redefineThat was some bad advice, thanks Jeremy Lakeman for pointing that out. IfGetHashCode, the compiler still generates its own version even if you specify your ownEquals(but that autogeneratedGetHashCodewill still use theList<T>'s default comparer'sGetHashCode).GetHashCodereturns different values for two instances thenEqualsmust return false for them, so you do in fact need to overrideGetHashCodeso that sequence-equal lists do not return different hashcodes, which would be the case with the default implementation.Disclaimer: the logic in the synthesised
Equalsis much more complex than this because it actually usesEqualityComparer<List<Point2d>>.Defaultand takes record inheritance into account, but it's irrelevant in this case.