2014-10-02 31 views
7

记录此再现的问题:TDictionary散列被分解为字符串

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

如何使用TObjectDictionary和自定义的IEqualityComparer来为您的特定类型实现GetHashCode? – 2014-10-02 12:44:47

+0

@VilleKrumlinde你不是指'TObjectDictionary'。这里没有对象所有权。 – 2014-10-02 12:57:17

+0

实现'IEqualityComparer'绝对是一种方法。 – 2014-10-02 18:52:39

回答

6

这是一个已知的限制,即通过设计。记录的默认比较器和哈希器仅用于纯值类型记录,而不适用于没有填充的记录。

设计人员可能选择使用RTTI来比较/散列记录。但是,他们选择不这样做。这种选择的一些显而易见的理由是:

  1. 他们不希望强制使用RTTI的解开。
  2. 使用RTTI会导致重大性能下降。

处理这个问题的方法是在使用泛型集合时提供自己的比较器和哈希器。

您当前的串联字符串策略不起作用。考虑'a''aa',然后'aa''a'。要使用基于文本的方法,您希望将记录序列化为JSON。

+0

我同意这对一般的参考类型有意义。然而,作为特殊字符串和编译器管理的字符串,我预料至少它们会以合理的方式默认散列(这看起来并不奇怪或不可能)。自定义比较器和哈希器。同意连接是一种垃圾策略 - 就我而言,根据具体问题的本质,这种做法实际上可以保证不会以您指出的方式发生冲突。这是一个临时的解决方法,直到我找到一个更好的策略。 – 2014-10-02 13:08:12

+0

这将如何实施?系统必须能够生成基于RTTI的比较器。我判断不平凡。 – 2014-10-02 13:09:43

+0

也许吧。我承认并没有认真对待它。无论如何,这一点是没有意义的。这将需要另一个解决方案。 – 2014-10-02 13:12:14

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; 

这个实现然后替换记录类型的默认代码。我的例子实际上使用一个类而不是记录,但我不明白为什么这不应该与记录类型完美地工作。请注意,比较器类是引用计数的,因此在字典被销毁时会自动处理。

+1

我没有看到这一点。在哪里实施。你似乎只是告诉我们你已经实现了一个比较器。FWIW正常的方法是调用Construct类的方法。 – 2014-10-02 22:50:07