记录泛型集合中的相等性

时间:2013-06-04 14:30:34

标签: delphi generics operator-overloading delphi-xe2 record

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

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;

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

2 个答案:

答案 0 :(得分: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这样的无序操作来说没问题,但显然根本没有用于排序,二进制搜索等等。

答案 1 :(得分:3)

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

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

List := TList<TSomeRecord>.Create( TComparer<TSomeRecord>.Construct(
  function ( const L, R : TSomeRecord ) : Integer
  begin
    Result := CompareStr( L.Value, R.Value );
  end ) );