TDictionary Hashing因字符串记录而被破坏

时间:2014-10-02 12:35:04

标签: delphi generics dictionary hash delphi-xe2

这再现了问题:

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>的字符串,但这自然不会令人满意。

2 个答案:

答案 0 :(得分:6)

这是一个已知的限制,即设计。记录的默认比较器和缓冲区仅适用于纯值类型记录,以及没有填充的记录。

设计人员可以选择使用RTTI来比较/散列记录。但是,他们选择不这样做。这种选择的一些明显合理的原因是:

  1. 他们不希望在不情愿的情况下强行使用RTTI。
  2. 使用RTTI会对性能造成重大影响。
  3. 处理此问题的方法是在使用通用集合时提供自己的比较器和缓冲区。

    您当前连接字符串的策略不起作用。考虑'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;

然后,为此实现替换记录类型的默认代码。我的例子实际上使用的是一个类而不是一个记录但是我不明白为什么这对于记录类型不能完全正常。请注意,比较器类是引用计数的,因此在字典被销毁时将自动处理。