Using TEqualityComparer<T>.Default
for record types seems to "work" in some situations ("work" as in checking for value-equivalence across all fields between two records), but not in others. I'm sure that writing a custom comparer is a better alternative over using this one, but I'm curious as to what's causing the difference.
Specifically, it seems like if the record has a String
field, calling Equals()
on the two records themselves works, but accessing them inside a list does not, eg:
type
TestRec = record
Value: String;
end;
begin
var Rec1: TestRec;
var Rec2: TestRec;
Rec1.Value := 'a';
Rec2.Value := 'a';
var List1: TArray<TestRec> := [Rec1];
var List2: TArray<TestRec> := [Rec2];
var Comparer: IEqualityComparer<TestRec>;
Comparer := TEqualityComparer<TestRec>.Default;
Comparer.Equals(Rec1, Rec2); // returns true
Comparer.Equals(List1[0], List2[0]); // returns false
end;
This doesn't seem to happen when the record doesn't contain managed types like String
, so I'm guessing it's something related to memory since the default comparer uses CompareMem()
.
Since your record type has a managed String
field, the Comparer
should not try to compare the raw memory of the two record instances, as it would be comparing the String
internal data pointers, not comparing the character data they point at.
But that is exactly what is happening in your example. In reality, TEqualityComparer
does raw memory comparisons, and so it only works with records that contain trivial non-managed fields, and have the same padding bytes in the record layout.
When you compare Rec1
and Rec2
directly, they compare as equal only because the two String
s happen to be pointing at the same memory block for the 'a'
literal. Change one of the String
s to a different value and they will not compare as equal anymore since their data pointers will be different. You can also force this in your example by calling UniqueString()
on the String
s, eg:
Rec1.Value := 'a';
Rec2.Value := 'a';
UniqueString(Rec1.Value);
UniqueString(Rec2.Value);
...
Comparer.Equals(Rec1, Rec2); // returns false!
When you compare the List
elements, the two String
s are pointing at separate memory blocks because the 'a'
data gets copied when each array is created, which is why the elements never compare as equal regardless of their values.
So, to compare a record type with managed fields, you MUST use a custom Comparer
that compares the record members one-by-one as appropriate for their types.