这再现了问题:
program Project1;
{$APPTYPE CONSOLE}
uses
Generics.Collections;
type
TStringRec = record
s1 : string;
s2 : string;
end;
TGetHash<TKey,TValue> = class(TEnumerable<TPair<TKey,TValue>>)
public
type
TItem = record
HashCode: Integer;
Key: TKey;
Value: TValue;
end;
TItemArray = array of TItem;
public
FItems: TItemArray;
end;
var
LCrossRef : TDictionary<TStringRec, integer>;
LRec : TStringRec;
i : integer;
begin
LCrossRef := TDictionary<TStringRec, integer>.Create();
LRec.s1 := 'test1';
LRec.s2 := 'test2';
LCrossRef.Add(LRec, 1);
LRec.s1 := 'test1';
LRec.s2 := 'test2';
if LCrossRef.TryGetValue(LRec, i) then begin
writeln('ok');
end else begin
LCrossRef.Add(LRec, 1);
for i := Low(TGetHash<TStringRec, integer>
(LCrossRef).FItems)
to High(TGetHash<TStringRec, integer>
(LCrossRef).FItems) do
WriteLn(TGetHash<TStringRec, integer>(LCrossRef).FItems[i].HashCode);
WriteLn('not ok');
end;
ReadLn;
end.
字典无法检索该项目,并为包含相同字符串的记录生成不同的HashCode
。
在QC-#122791中部分注明了这一点,但使用打包记录的解决方法对字符串记录不起作用(至少上述示例在TStringRec
声明为packed record
时也失败)
这是否有合理的解决方法?
我目前的策略是连接原本会进入记录并使用TDictionary<string, TValue>
的字符串,但这自然不会令人满意。
答案 0 :(得分:6)
这是一个已知的限制,即设计。记录的默认比较器和缓冲区仅适用于纯值类型记录,以及没有填充的记录。
设计人员可以选择使用RTTI来比较/散列记录。但是,他们选择不这样做。这种选择的一些明显合理的原因是:
处理此问题的方法是在使用通用集合时提供自己的比较器和缓冲区。
您当前连接字符串的策略不起作用。考虑'a'
和'aa'
,然后'aa'
和'a'
。要使用基于文本的方法,您需要将记录序列化为JSON。
答案 1 :(得分:2)
使用我的代码库中的示例扩展David的答案。我有一本字典
Records: TDictionary<TGazetteerRecord,TGazetteerRecord>
实例化
Records := TDictionary<TGazetteerRecord,TGazetteerRecord>.Create(InitCapacity, TGazRecordComparer.Create);
使这项工作的原因是在构建字典时使用自定义比较器。
TGazRecordComparer = class(TEqualityComparer<TGazetteerRecord>)
private
public
function Equals(const Left, Right: TGazetteerRecord): Boolean; override;
function GetHashCode(const Value: TGazetteerRecord): Integer; override;
end;
然后,为此实现替换记录类型的默认代码。我的例子实际上使用的是一个类而不是一个记录但是我不明白为什么这对于记录类型不能完全正常。请注意,比较器类是引用计数的,因此在字典被销毁时将自动处理。