Equals и GetHashCode для TDictionary - PullRequest
       44

Equals и GetHashCode для TDictionary

2 голосов
/ 15 марта 2011

Если я реализую отношение Car <-> Owner в Delphi с использованием TDictionary, как мне следует реализовать функцию Equals и GetHashCode в IEqualityComparer? (GetHashCode возвращает целое число, которое используется для хеширования в TDictionary.)

Для класса TVehicle предположим, что он имеет VIN (идентификационный номер транспортного средства).

Как мне реализовать хеш-код для VIN?

Обновление: в этом примере идентичность объекта означает не «идентичность областей памяти двух указателей объекта», а «идентичность двух экземпляров одного и того же объекта на основе уникального и неизменного (« неизменяемого") сочетание его свойств '.

Таким образом, вместо того, чтобы искать транспортное средство по его адресу в памяти на карте, мне нужен автомобиль с идентификатором, который я ищу.

Представьте себе базу данных, которая содержит данные о владельце автомобиля, загруженные в словарь при запуске приложения. Теперь, если пользователь вводит VIN в форму заявки, как приложение может найти транспортное средство в словаре? Если код создает новый экземпляр с использованием VehicleFactory.CreateVehicleFromDatabase(Edit1.Text); и выполняет поиск этого объекта в словаре, реализация Equals по умолчанию не найдет никаких записей на карте, поскольку ищет адрес памяти. Чтобы найти транспортное средство, Равным необходимо сравнить VIN.

Так что мне нужно создать собственный IEqualityComparer. Реализация Равных тривиальна. Но как насчет GetHashCode? Для свойства строки я не могу просто использовать адрес строки (см. Берри Келли в Являются ли строки Delphi неизменными? : «Если вы создадите одну и ту же строку из двух отдельных разделов кода, они не будут совместно использоваться то же самое хранилище "), поэтому функция GetHashCode для строкового свойства нуждается в настраиваемой реализации.

Я также обнаружил, что нашел вопрос Как мне хэшировать строку с Delphi? - есть пример, который содержит HashValue('Hello World')

Ответы [ 3 ]

3 голосов
/ 15 марта 2011

Если вам сообщают о запахе в вашем дизайне и других вещах, я отвечу на ваш вопрос, так как действительно создать словарь с ключами объекта и сравнить его на основе чего-либо, отличного от адреса памяти ключа:

Вы можете создать новый компаратор во время создания 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

Это говорит о том, что я считаю недостатком в вашем коде, если у вас есть два экземпляра, представляющих один и тот же объект. Если у вас в памяти есть телевизор с идентификатором «ABC», то для меня это должен быть единственный экземпляр этого транспортного средства, и вы должны предоставить какой-то способ получить этот же экземпляр для всего своего кода. Таким образом, вы можете использовать класс Dictionary без написания пользовательского компаратора, но, что более важно, вы знаете, что все время работаете с одним и тем же объектом, и состояние вашего приложения будет соответствовать и выглядеть согласованным с любым кодом в пользовательском интерфейсе или других интерфейсах .

3 голосов
/ 16 марта 2011

Вы, кажется, ошиблись, полагая, что строки Delphi не содержат реализацию хеш-кода по умолчанию.

Это не так.Когда вы создаете TDictionary со строковым значением в качестве ключа, хэш вычисляется на основе содержимого строки.Если Value является строковой переменной, то код выглядит следующим образом:

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

Я думаю, что это отвечает на часть вашего вопроса, касающегося хеширования строк.


Комментарии кдругие ответы, и те, которые я удалил, были интересным обсуждением проблемы дизайна, которую вы рассматриваете.Я по-прежнему скептически отношусь к вашей уверенности в том, что правильное решение состоит в том, чтобы разрешить отношения «один к одному» между экземплярами TVehicle и VIN.

Вы подтвердили, что у вас не должно быть нескольких экземпляров TVehicle с одинаковым VIN,разные данные.Мне кажется, что лучший способ добиться этого - убедиться, что у вас есть отношения один-к-одному между экземплярами TVehicle и VIN.

Эти отношения один-к-одному довольно легко достичь.Вам нужно сделать экземпляры экземпляров TVehicle функцией, частной для класса фабрики.Этот фабричный класс содержит словарь, содержащий существующие экземпляры транспортных средств, TDictionary<string,TVehicle>.Если вам нужно завладеть транспортным средством, обратитесь за консультацией к производителю.Он возвращает либо существующий, который был расположен в его словаре, либо синтезирует новый.

Нет никаких сомнений в ряде других способов достижения этого эффекта, но я настоятельно призываю вас рассмотреть подход, которыйВ результате получается только один экземпляр транспортного средства на VIN.

3 голосов
/ 15 марта 2011

Я бы бросил принцип KISS на этот, если это возможно. Если ваш настоящий ключ - это идентификационный номер, а не само транспортное средство, то почему бы не использовать TDictionary<string, TPerson> вместо TDictionary<TVehicle, TPerson>? Тогда вам не придется беспокоиться о пользовательских компараторах.

...