TDictionary的 Equals 和 GetHashCode

发布于 2024-10-22 05:52:23 字数 1104 浏览 7 评论 0原文

如果我实现一个关系 Car <->在Delphi中使用TDictionary的所有者,我应该如何实现IEqualityComparer的Equals和GetHashCode函数? (GetHashCode 返回一个 Integer,用于 TDictionary 中的散列。)

对于 TVehicle 类,假设它有一个 VIN(车辆识别号)。

我应该如何实现VIN的哈希码?

更新:在这个例子中,对象标识并不意味着“两个对象指针的内存位置的标识”,而是“同一对象的两个实例的标识” ,基于其属性的独特且不变(“不可变”)组合。

因此,我不需要通过地图中的内存地址来搜索车辆,而是需要具有我要查找的 ID 的车辆。

想象一个包含车主数据的数据库,在应用程序启动时加载到字典中。现在,如果用户在申请表中输入 VIN,应用程序如何在字典中找到车辆?如果代码使用 VehicleFactory.CreateVehicleFromDatabase(Edit1.Text); 创建一个新实例并在字典中搜索此对象,则 Equals 的默认实现将不会在映射中找到任何条目,因为它看起来为内存地址。为了找到车辆,Equals 需要比较 VIN。

所以我必须创建一个自定义 IEqualityComparer。实现 Equals 很简单。但是 GetHashCode 呢?对于字符串属性,我不能简单地使用字符串的地址(请参阅 中的 Berry Kelly Delphi 字符串是不可变的吗?:“如果从两个独立的代码部分创建相同的字符串,它们将不会共享相同的后备存储”),因此字符串属性的 GetHashCode 函数需要自定义实现。

我还发现我发现了问题 How do I hash a string with Delphi? - 有一个包含 HashValue('Hello World') 的示例

If I implement a relationship Car <-> Owner in Delphi using a TDictionary, how should I implement the Equals and GetHashCode function of the IEqualityComparer? (GetHashCode returns an Integer which is used for hashing in TDictionary.)

For the TVehicle class, assume that it has a VIN (vehicle identification number).

How should I implement the hashcode for the VIN?

Update: in this example, object identity does not mean 'identity of the memory locations of two object pointers', but 'identity of two instances of the same object, based on a unique and invariable ("immutable") combination of its properties'.

So instead of searching a vehicle by its memory address in the map, I need the vehicle which has the id I am looking for.

Think of a database which contains the vehicle-owner data, loaded into the dictionary at application startup. Now if the user enters a VIN in an application form, how can the application find the vehicle in the dictionary? If the code creates a new instance using VehicleFactory.CreateVehicleFromDatabase(Edit1.Text); and search for this object in the dictionary, the default implementation of Equals will not find any entries in the map, because it looks for the memory address. To find the vehicle, Equals needs to compare the VIN.

So I have to create a custom IEqualityComparer. Implementing Equals is trivial. But what about GetHashCode? For a string property, I can not simply use the address of the string (see Berry Kelly in Are Delphi strings immutable? : "If you create the same string from two separate sections of code, they will not share the same backing store"), so a GetHashCode function for a string property needs a customized implementation.

I also found I found the question How do I hash a string with Delphi? - there is an example which contains a HashValue('Hello World')

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

风吹短裙飘 2024-10-29 05:52:23

您似乎被误导了,认为 Delphi 字符串没有默认的哈希代码实现。

事实并非如此。当您创建一个以字符串值作为键的 TDictionary 时,将根据字符串的内容计算哈希值。如果 Value 是一个字符串变量,那么代码如下所示:

BobJenkinsHash(Value[1], Length(Value) * SizeOf(Value[1]), 0);

我认为这回答了您关于字符串散列的问题的部分。


对其他答案的评论以及我已删除的评论是关于您正在考虑的设计问题的有趣讨论。我仍然对您的信念表示怀疑,即正确的解决方案是允许 TVehicle 实例和 VIN 之间存在多对一关系。

您已确认您不得拥有多个具有相同 VIN 但数据不同的 TVehicle 实例。在我看来,实现这一目标的最佳方法是确保 TVehicle 实例和 VIN 之间存在一对一的关系。

这种一对一的关系很容易实现。您需要将 TVehicle 实例的实例化设为工厂类的私有函数。该工厂类保存一个包含现有车辆实例的字典,TDictionary。如果您需要获得车辆,请向工厂索要。它返回位于其字典中的现有字典,或者合成一个新字典。

毫无疑问,还有许多其他方法可以实现此效果,但我强烈建议您考虑一种方法,使每个 VIN 仅生成一个车辆实例。

You appear to have been misled into the belief that Delphi strings do not come with a default hash code implementation.

This is not the case. When you create a TDictionary with a string value as a key, the hash is calculated based on the contents of the string. If Value is a string variable then the code looks like this:

BobJenkinsHash(Value[1], Length(Value) * SizeOf(Value[1]), 0);

I think this answers the part of your question concerning string hashing.


The comments to the other answers, and the ones I have deleted were an interesting discussion on the design problem you are contemplating. I'm still sceptical of your belief that the right solution is to allow a many-to-one relationship between TVehicle instances and VIN.

You have confirmed that you must not have multiple TVehicle instances with the same VIN but differing data. It seems to me that the best way to achieve this is to ensure that you have a one-to-one relationship between TVehicle instances and VIN.

This one-to-one relationship is quite easy to achieve. You need to make the instantiation of TVehicle instances a function private to a factory class. This factory class holds a dictionary containing the existing vehicle instances, TDictionary<string,TVehicle>. If you need to get hold of a vehicle you ask the factory for it. It returns either an existing one that was located in its dictionary, or synthesises a new one.

There are no doubt a number of other ways to achieve this effect, but I would strongly urge you to consider an approach that results in only a single vehicle instance per VIN.

疯到世界奔溃 2024-10-29 05:52:23

如果可能的话,我会在这个问题上运用 KISS 原则。如果您的实际密钥是 ID 而不是车辆本身,那么为什么不使用 TDictionary 而不是 TDictionary 呢?那么您就不必担心自定义比较器。

I'd throw the KISS principle at this one, if possible. If your actual key is the ID and not the vehicle itself, then why not use a TDictionary<string, TPerson> instead of TDictionary<TVehicle, TPerson>? Then you wouldn't have to worry about custom comparers.

奈何桥上唱咆哮 2024-10-29 05:52:23

当您被告知设计中的气味和其他事情时,我将回答您的问题,因为创建对象键控字典并基于与键的内存地址不同的任何内容进行比较是有效的:

您可以创建一个新的比较器在 TDictionary 创建时。

例如:

type
  TVehicleOwner = class (TDictionary<TVehicle, TOwner>)
  end;

//other code here

procedure TForm2.Button1Click(Sender: TObject);
var
  VehOwner: TVehOwner;
begin
  VehOwner := TVehOwner.Create(TEqualityComparer<TVehicle>.Construct(
    //comparer
    function(const Left, Right: TVehicle): Boolean
    begin
      { Make a case insensitive comparison }
      Result := CompareText(Left.FID, Right.FID) = 0;
    end,
    //hasher
    function(const Value: TVehicle): Integer
    begin
      { Generate a hash code. }
      Result := TheHashAlgorythmOfYourChoice(Value.FID);
    end)
  );

  //more code here

这就是说,如果你有两个代表同一个对象的实例,我认为你的代码中有一个缺陷。如果您有一辆内存中 ID 为“ABC”的 TVehicle,对于我来说,这应该是该车辆的唯一实例,并且您必须提供某种方法来为所有代码获取相同的实例。这样,您可以使用 Dictionary 类,而无需编写自定义比较器,但更重要的是,您知道您一直在使用同一个对象,并且您的应用程序状态将与代码、UI 或其他界面中的任何内容保持一致。 。

Being you advised about a smell in your design and other things, I'll respond your question as it is valid to create a object keyed dictionary and compare it based in anything different than the memory address of the key:

You can create a new comparer at TDictionary creation time.

For example:

type
  TVehicleOwner = class (TDictionary<TVehicle, TOwner>)
  end;

//other code here

procedure TForm2.Button1Click(Sender: TObject);
var
  VehOwner: TVehOwner;
begin
  VehOwner := TVehOwner.Create(TEqualityComparer<TVehicle>.Construct(
    //comparer
    function(const Left, Right: TVehicle): Boolean
    begin
      { Make a case insensitive comparison }
      Result := CompareText(Left.FID, Right.FID) = 0;
    end,
    //hasher
    function(const Value: TVehicle): Integer
    begin
      { Generate a hash code. }
      Result := TheHashAlgorythmOfYourChoice(Value.FID);
    end)
  );

  //more code here

This said, I think it is a flaw in your code if you have two instances representing the same object. If you have a TVehicle with the ID 'ABC' in memory, as for me, this should be the only instance of that vehicle and you have to provide some way to get this same instance for all of your code. That way, you can use the Dictionary class without writing a custom comparer, but more important, you know you're working all the time with the same object and your application state will be and appear consistent to anything in code, UI or other interfaces.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文