假设您有一个带有重载相等运算符的记录
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;
这是预期的行为吗?有什么解释吗?
解决方法
假设您没有在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; )
对于像IndexOf或Contains这样的无序操作,这很好,但显然根本不用于排序,二进制搜索等.