delphi - 使用对象作为 TObjectDictionary 中的键

标签 delphi delphi-xe2

当我使用 TObjectDictionary(其中 TKey 是对象)时,我的应用程序无法正常工作。 我有两个单元,包含两个类。第一单元:

unit RubTerm;

interface

type
  TRubTerm = Class(TObject)
  private
    FRubricName: String;
    FTermName: String;
  public
    property RubricName: String read FRubricName;
    property TermName: String read FTermName;
    constructor Create(ARubricName, ATermName: String);
  end;

implementation

constructor TRubTerm.Create(ARubricName, ATermName: String);
begin
  Self.FRubricName := ARubricName;
  Self.FTermName := ATermName;
end;

end;

第二个单元:

unit ClassificationMatrix;

interface

uses
  System.Generics.Collections, System.Generics.Defaults, System.SysUtils, RubTerm;

type
TClassificationMatrix = class(TObject)
  private
    FTable: TObjectDictionary<TRubTerm, Integer>;
  public
    constructor Create;
    procedure TClassificationMatrix.AddCount(ADocsCount: Integer; ARubName, ATermName: String);
    function TClassificationMatrix.GetCount(ARubName, ATermName: String): Integer;
  end;

implementation

constructor TClassificationMatrix.Create;
begin
  FTable := TObjectDictionary<TRubTerm, Integer>.Create;
end;

procedure TClassificationMatrix.AddCount(ADocsCount: Integer; ARubName, ATermName: String);
var
  ARubTerm: TRubTerm;
begin
  ARubTerm := TRubTerm.Create(ARubName, ATermName);
  FTable.Add(ARubTerm, ADocsCount);
end;

function TClassificationMatrix.GetCount(ARubName, ATermName: String): Integer;
var
  ARubTerm: TRubTerm;
begin
  ARubTerm := TRubTerm.Create(ARubName, ATermName);
  FTable.TryGetValue(ARubTerm, Result);
end;

end;

但是这段代码工作不正常:

procedure TestTClassificationMatrix.TestGetCount;
var
  DocsCountTest: Integer;
begin
  FClassificationMatrix.AddCount(10, 'R', 'T');
  DocsCountTest := FClassificationMatrix.GetCount('R', 'T');
end;
// DocsCountTest = 0! Why not 10? Where is problem?

谢谢!

最佳答案

这里的根本问题是您的类型的默认相等比较器的行为与您希望的方式不同。您希望相等意味着值相等,但默认比较给出引用相等

您希望值相等的事实强烈表明您应该使用值类型而不是引用类型。这是我建议的第一个改变。

type
  TRubTerm = record
    RubricName: string;
    TermName: string;
    class function New(const RubricName, TermName: string): TRubTerm; static;
    class operator Equal(const A, B: TRubTerm): Boolean;
    class operator NotEqual(const A, B: TRubTerm): Boolean;
  end;

class function TRubTerm.New(const RubricName, TermName: string): TRubTerm;
begin
  Result.RubricName := RubricName;
  Result.TermName := TermName;
end;

class operator TRubTerm.Equal(const A, B: TRubTerm): Boolean;
begin
  Result := (A.RubricName=B.RubricName) and (A.TermName=B.TermName);
end;

class operator TRubTerm.NotEqual(const A, B: TRubTerm): Boolean;
begin
  Result := not (A=B);
end;

我已添加TRubTerm.New作为辅助方法,可以轻松初始化记录的新实例。为了方便起见,您可能还会发现重载相等和不等运算符很有用,就像我上面所做的那样。

切换到值类型后,您还需要更改字典以进行匹配。使用TDictionary<TRubTerm, Integer>而不是TObjectDictionary<TRubTerm, Integer> 。切换到值类型还可以修复现有代码中的所有内存泄漏。您现有的代码创建对象但从不销毁它们。

这已经让你回家了,但你仍然需要为你的字典定义一个相等比较器。记录的默认比较器将基于引用相等,因为字符串尽管表现为值类型,但仍存储为引用。

要制作合适的相等比较器,您需要实现以下比较函数,其中 T替换为 TRubTerm :

TEqualityComparison<T> = reference to function(const Left, Right: T): Boolean;
THasher<T> = reference to function(const Value: T): Integer;

我将它们实现为记录的静态类方法。

type
  TRubTerm = record
    RubricName: string;
    TermName: string;
    class function New(const RubricName, TermName: string): TRubTerm; static;
    class function EqualityComparison(const Left, 
      Right: TRubTerm): Boolean; static;
    class function Hasher(const Value: TRubTerm): Integer; static;
    class operator Equal(const A, B: TRubTerm): Boolean;
    class operator NotEqual(const A, B: TRubTerm): Boolean;
  end;

实现EqualityComparison很简单:

class function TRubTerm.EqualityComparison(const Left, Right: TRubTerm): Boolean;
begin
  Result := Left=Right;
end;

但是哈希器需要更多的思考。您需要单独对每个字段进行散列,然后组合散列。供引用:

代码如下所示:

{$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}

function GetHashCodeString(const Value: string): Integer;
begin
  Result := BobJenkinsHash(PChar(Value)^, SizeOf(Char) * Length(Value), 0);
end;

class function TRubTerm.Hasher(const Value: TRubTerm): Integer;
begin
  Result := CombinedHash([GetHashCodeString(Value.RubricName), 
    GetHashCodeString(Value.TermName)]);
end;

最后,当你实例化你的字典时,你需要提供一个 IEqualityComparison<TRubTerm> 。像这样实例化你的字典:

Dict := TDictionary<TRubTerm,Integer>.Create(
  TEqualityComparer<TRubTerm>.Construct(
    TRubTerm.EqualityComparison,
    TRubTerm.Hasher
  )
);

关于delphi - 使用对象作为 TObjectDictionary 中的键,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18068977/

相关文章:

delphi - 使用计时器将Delphi表单最小化到系统托盘

delphi - 为什么 bool 表达式(有副作用)不足以作为语句?

python - 检测反射 DLL 注入(inject)

delphi - 如何实现IsFirstRecord和IsLastRecord?

delphi - Delphi 中如何将接口(interface)转换为对象

delphi - 自定义 QuickSort 实现中的 SIGSEGV

德尔福XE2 : Invoke WinAPI EnumResourceNames cause access violation in Win64 platform

delphi - 寻找具有良好单元测试覆盖率的开源Delphi项目

delphi 仅调试我的源代码

delphi - MSXML XPath 可以选择属性吗? (更新: real issue was with default no-prefix namespace )