Record equality in generic collections

两盒软妹~` 提交于 2019-12-05 04:15:31

Assuming that you did not specify a comparer in the constructor to TList.Create you will get TComparer<TSomeRecord>.Default as your comparer. And that is a comparer that performs simple binary comparison using CompareMem.

That's fine for a record full of value types, with no padding. But otherwise you will need to supply your own compare function when you instantiate the list.

If you want to look at the details, the default comparer for records is implemented in Generics.Defaults. For larger records the equality comparer is this function:

function Equals_Binary(Inst: PSimpleInstance; const Left, Right): Boolean;
begin
  Result := CompareMem(@Left, @Right, Inst^.Size);
end;

For smaller records there is an optimization and your comparer will be the 4 byte comparer. That looks like this:

function Equals_I4(Inst: Pointer; const Left, Right: Integer): Boolean;
begin
  Result := Left = Right;
end;

That's a bit weird, but it interprets the 4 bytes of your record as a 4 byte integer and performs integer equality comparison. In other words, the same as CompareMem, but more efficient.

The comparer that you want to use might look like this:

TComparer<TSomeRecord>.Construct(
  function const Left, Right: TSomeRecord): Integer
  begin
    Result := CompareStr(Left.Value, Right.Value);
  end;
)

Use CompareText if you want case insensitive, and so on. I've used an ordered comparison function because that's what TList<T> wants.

The fact that the default record comparison is an equality comparison tells you that attempts to sort lists of records without specifying your own comparer will have unexpected results.

Given that the default comparer uses an equality comparison tells you that it would not be totally unreasonable to use a comparer like this:

TComparer<TSomeRecord>.Construct(
  function const Left, Right: TSomeRecord): Integer
  begin
    Result := ord(not (Left = Right));
  end;
)

That will be fine for unordered operations like IndexOf or Contains but obviously no use at all for sorting, binary search and so on.

To get the expected behavior you have to create the List with a comparer.

In this case you should use

List := TList<TSomeRecord>.Create( TComparer<TSomeRecord>.Construct(
  function ( const L, R : TSomeRecord ) : Integer
  begin
    Result := CompareStr( L.Value, R.Value );
  end ) );
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!