最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

delphi - Why does the default equality comparer for records behave differently when the record contains certain fields and is ac

programmeradmin5浏览0评论

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().

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().

Share Improve this question edited Mar 27 at 1:20 Remy Lebeau 601k36 gold badges507 silver badges851 bronze badges asked Mar 26 at 23:39 easelyeasely 533 bronze badges 1
  • 2 fwiw TEqualityComparer<T> is for collections like dictionaries that need a way to call GetHashCode and Equals for any type. If you have a concrete type, you don't need to use an IEqualityComparer<T> to check for equality - just implement the Equals operator for your record type and use =. – Stefan Glienke Commented Mar 27 at 8:34
Add a comment  | 

1 Answer 1

Reset to default 6

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 Strings happen to be pointing at the same memory block for the 'a' literal. Change one of the Strings 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 Strings, 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 Strings 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.

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论