在泛型集合中记录相等性 [英] Record equality in generic collections

查看:155
本文介绍了在泛型集合中记录相等性的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

  TSomeRecord = record 
Value:String;
类运算符Equal(Left,Right:TSomeRecord):Boolean;
end;

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

  var 
列表:TList< TSomeRecord>;
Record1,
Record2:TSomeRecord;

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

断言(List.Contains(Record1));
Assert(List.Contains(Record2)); //< ---这不是真的
end;

这是预期的行为吗?任何解释?假设你没有在构造函数中为指定一个比较器TList.Create

/ code>你会得到 TComparer< TSomeRecord> .Default 作为你的比较器。这是一个比较器,它使用 CompareMem 执行简单的二进制比较。



对于记录完整的值类型,没有填充。但是,除非实例化列表,否则您将需要提供自己的比较函数。



如果您想查看详细信息,记录的默认比较器在 Generics.Defaults 。对于较大的记录,相等比较器是这个函数:

 函数Equals_Binary(Inst:PSimpleInstance; const Left,Right):Boolean; 
begin
结果:= CompareMem(@Left,@ Right,Inst ^ .Size);
end;

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

 函数Equals_I4(Inst:Pointer; const Left,Right:Integer):Boolean; 
开始
结果:=左=右;
end;

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



您想使用的比较器可能看起来像像这样:
$ b $ pre $ TComparer< TSomeRecord> .Construct(
function const Left,Right:TSomeRecord):Integer
begin
结果:= CompareStr(Left.Value,Right.Value);
end;

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



默认记录比较是等值比较的事实告诉您,尝试对记录列表进行排序而不指定自己的比较器会产生意想不到的结果。 b
$ b

考虑到默认比较器使用等式比较告诉您使用像这样的比较器并不是完全不合理的:



<$ p (
函数const Left,Right:TSomeRecord):整数
begin
结果:= ord(not(Left = Right))$ p $ TComparer< TSomeRecord> .Construct );
end;

对于无序操作,比如 IndexOf 包含,但显然根本没有用于排序,二进制搜索等。


Assume you have a record with an overloaded equality operator

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

(implementation compares string values). If adding two records to the list that are equal based on the overloaded operator I would expect the Contains method to return true in both cases. But in fact, the generic list seems to just compare the memory content of records instead of applying the overloaded equality operator.

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;

Is this the expected behaviour? Any explanations?

解决方案

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.

这篇关于在泛型集合中记录相等性的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆