作者:qgone | 来源:互联网 | 2023-02-07 15:09
我们最近遇到了一个问题,TDictionary
即无法正确查找已包含在字典中的项目.该问题仅发生在64位版本中.我能够将问题分解为这段代码:
var
i1, i2: TPair;
begin
FillMemory(@i1, sizeof(i1), $00);
FillMemory(@i2, sizeof(i1), $01);
i1.Key := 2;
i1.Value := -1;
i2.Key := i1.Key;
i2.Value := i1.Value;
Assert(TEqualityComparer>.Default.Equals(i1, i2));
Assert(TEqualityComparer>.Default.GetHashCode(i1) = TEqualityComparer>.Default.GetHashCode(i2));
end;
断言在Win64版本中失败.由于记录对齐,似乎会出现问题:此TPair的大小为16个字节,但只有12个字节用数据填充.该TEqualityComparer
然而通吃16个字节考虑.所以2个记录值可能被视为不相等,尽管所有成员都相等,只是因为内存的先前内容不同
这可以被视为设计中的错误或行为吗?无论如何,这是一个陷阱.这种情况的最佳解决方案是什么?
作为解决方法,可以使用NativeInt
而不是Integer
,但是这种Integer
类型不在我们的控制之下.
1> David Heffer..:
我不认为这是一个错误.行为是设计的.如果没有检查或者可能需要一些编译时间来理解这些类型,就很难为任意结构化类型编写通用比较器.
默认记录比较器只能安全地用于没有填充的类型,并且只包含可以使用朴素二进制比较进行比较的某些简单值类型.例如,浮点类型已经输出,因为它们的比较运算符更复杂.想想NaNs,负零等等.
我认为解决这个问题的唯一有效方法是编写自己的相等比较器.其他人建议默认初始化所有记录实例,但这给这类类型的消费者带来了沉重的负担,并且在某些代码忘记默认初始化的情况下,存在模糊和难以追踪缺陷的风险.
我会TEqualityComparer.Construct
用来创建合适的相等比较器.这需要最少量的样板.您提供了两个匿名方法:一个等于函数和一个哈希函数,并Construct
返回一个新的比较器.
你可以将它包装在一个泛型类中,如下所示:
uses
System.Generics.Defaults,
System.Generics.Collections;
{$IFOPT Q+}
{$DEFINE OverflowChecksEnabled}
{$Q-}
{$ENDIF}
function CombinedHash(const Values: array of Integer): Integer;
var
Value: Integer;
begin
Result := 17;
for Value in Values do begin
Result := Result*37 + Value;
end;
end;
{$IFDEF OverflowChecksEnabled}
{$Q+}
{$ENDIF}
type
TPairComparer = class abstract
public
class function Construct(
const EqualityComparison: TEqualityComparison>;
const Hasher: THasher>
): IEqualityComparer>; overload; static;
class function Construct: IEqualityComparer>; overload; static;
end;
class function TPairComparer.Construct(
const EqualityComparison: TEqualityComparison>;
const Hasher: THasher>
): IEqualityComparer>;
begin
Result := TEqualityComparer>.Construct(
EqualityComparison,
Hasher
);
end;
class function TPairComparer.Construct: IEqualityComparer>;
begin
Result := Construct(
function(const Left, Right: TPair): Boolean
begin
Result :=
TEqualityComparer.Default.Equals(Left.Key, Right.Key) and
TEqualityComparer.Default.Equals(Left.Value, Right.Value);
end,
function(const Value: TPair): Integer
begin
Result := CombinedHash([
TEqualityComparer.Default.GetHashCode(Value.Key),
TEqualityComparer.Default.GetHashCode(Value.Value)
]);
end
)
end;
我提供了两个重载.如果两种类型的默认比较器足够,则可以使用无参数过载.否则,您可以提供两种匿名方法来定制类型.
对于您的类型,您将获得这样的比较器:
TPairComparer.Construct
这两种简单类型都有可以使用的默认相等比较器.因此Construct
可以使用无参数过载.