记录泛型集合中的相等性

jpf*_*ius 8 delphi generics record operator-overloading delphi-xe2

假设您有一个带有重载相等运算符的记录

TSomeRecord = record
  Value : String;
  class operator Equal(Left, Right : TSomeRecord) : Boolean;
end;
Run Code Online (Sandbox Code Playgroud)

(实现比较字符串值).如果根据重载运算符向列表中添加两个相等的记录,我希望该Contains方法在两种情况下都返回true.但实际上,通用列表似乎只是比较记录的内存内容而不是应用重载的相等运算符.

var
  List : TList <TSomeRecord>;
  Record1,
  Record2 : TSomeRecord;

begin
Record1.Value := 'ABC';
Record2.Value := 'ABC';
List.Add(Record1);

Assert(List.Contains(Record1));
Assert(List.Contains(Record2));    //  <--- this is not true
end;
Run Code Online (Sandbox Code Playgroud)

这是预期的行为吗?有什么解释吗?

Dav*_*nan 8

假设您没有在构造函数中指定比较器,那么TList.Create您将获得TComparer<TSomeRecord>.Default比较器.这是一个比较器,使用执行简单的二进制比较CompareMem.

这对于满足值类型的记录来说很好,没有填充.但是,在实例化列表时,您需要提供自己的比较功能.

如果要查看详细信息,则会在中实现记录的默认比较器Generics.Defaults.对于较大的记录,相等比较器是此函数:

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

对于较小的记录,存在优化,您的比较器将是4字节比较器.看起来像这样:

function Equals_I4(Inst: Pointer; const Left, Right: Integer): Boolean;
begin
  Result := Left = Right;
end;
Run Code Online (Sandbox Code Playgroud)

这有点奇怪,但它将记录的4个字节解释为4字节整数并执行整数相等比较.换句话说,相同CompareMem,但更有效.

您要使用的比较器可能如下所示:

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

CompareText如果您想要不区分大小写,请使用,依此类推.我使用了有序的比较函数,因为这就是你TList<T>想要的.

默认记录比较是相等比较的事实告诉您,在不指定自己的比较器的情况下对记录列表进行排序的尝试将产生意外结果.

鉴于默认比较器使用相等比较,告诉您使用这样的比较器是不完全不合理的:

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

这将是罚款,像无序操作IndexOfContains但显然没有用在所有的排序,二进制搜索等.