8

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

TSomeRecord = record
  Value : String;
  class operator Equal(Left, Right : TSomeRecord) : Boolean;
end;

(实现比较字符串值)。如果基于重载运算符将两个相等的记录添加到列表中,我希望该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;

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

4

2 回答 2

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;

对于较小的记录,有一个优化,您的比较器将是 4 字节比较器。看起来像这样:

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

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

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

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

CompareText如果您想要不区分大小写等,请使用。我使用了一个有序的比较函数,因为这就是我们TList<T>想要的。

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

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

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

这对于无序操作来说很好,IndexOfContains显然对于排序、二分搜索等毫无用处。

于 2013-06-04T14:43:59.960 回答
3

要获得预期的行为,您必须使用比较器创建列表。

在这种情况下,您应该使用

List := TList<TSomeRecord>.Create( TComparer<TSomeRecord>.Construct(
  function ( const L, R : TSomeRecord ) : Integer
  begin
    Result := CompareStr( L.Value, R.Value );
  end ) );
于 2013-06-04T14:50:58.697 回答