Equals и gethashcode для tdictionary
-
24-10-2019 - |
Вопрос
Если я внедряю автомобиль отношений <--> в Delphi, используя Tdictionary, как мне реализовать функцию equals и gethashcode в iequalityComparer? (Gethashcode возвращает целое число, которое используется для хэширования в Tdictionary.)
Для класса Tvehicle предположим, что он имеет VIN (номер идентификации транспортного средства).
Как мне реализовать хэшкод для VIN?
Обновление: в этом примере идентичность объекта не означает «идентичность местоположений памяти двух указателей объекта», а «идентичность двух экземпляров одного и того же объекта, основанного на уникальном и неизбежном (»неизменный«) Комбинация его свойств».
Поэтому вместо того, чтобы искать автомобиль по адресу памяти на карте, мне нужен автомобиль, у которого есть идентификатор, который я ищу.
Подумайте о базе данных, которая содержит данные владельца транспортного средства, загруженную в словарь при запуске приложения. Теперь, если пользователь входит в VIN в форму заявки, как приложение может найти автомобиль в словаре? Если код создает новый экземпляр с использованием VehicleFactory.CreateVehicleFromDatabase(Edit1.Text);
и поиск этого объекта в словаре, реализация равных по умолчанию не найдет никаких записей на карте, потому что он ищет адрес памяти. Чтобы найти транспортное средство, Equals необходимо сравнить VIN.
Поэтому я должен создать пользовательский iequalityComperer. Реализация равна тривиальна. Но как насчет Gethashcode? Для свойства строки я не могу просто использовать адрес строки (см. Берри Келли в Неужели Delphi Strings неизменны? : «Если вы создаете одну и ту же строку из двух отдельных разделов кода, они не будут делиться одним и тем же хранилищем поддержки»), поэтому функция GethashCode для свойства строки нуждается в настраиваемой реализации.
Я также обнаружил, что нашел вопрос Как мне хэт -строку с Delphi? - Есть пример, который содержит HashValue('Hello World')
Решение
Похоже, вы были введены в заблуждение в убеждении, что строки Delphi не поставляются с реализацией хэш -кода по умолчанию.
Это не тот случай. Когда вы создаете TDictionary
С помощью строкового значения в качестве ключа хэш рассчитывается на основе содержимого строки. Если Value
это строковая переменная, тогда код выглядит так:
BobJenkinsHash(Value[1], Length(Value) * SizeOf(Value[1]), 0);
Я думаю, что это отвечает на часть вашего вопроса, касающегося строкового хеширования.
Комментарии к другим ответам, и те, которые я удалил, были интересным обсуждением проблемы дизайна, которую вы рассматриваете. Я все еще скептически отношусь к вашей убеждению, что правильное решение состоит в том, чтобы позволить взаимосвязь между экземплярами Twehicle и VIN.
Вы подтвердили, что у вас не должно быть нескольких экземпляров Tvehicle с тем же VIN, но разные данные. Мне кажется, что лучший способ достичь этого-убедиться, что у вас есть отношения один к одному между экземплярами и ВИН.
Эти отношения один к одному довольно легко достичь. Вам нужно сделать экземпляр экземпляров TVEHICLE функцией частной для фабричного класса. Этот фабричный класс имеет словарь, содержащий существующие экземпляры транспортных средств, TDictionary<string,TVehicle>
. Анкет Если вам нужно получить автомобиль, вы спросите за фабрикой. Он возвращает либо существующий, который был расположен в его словаре, либо синтезирует новый.
Нет сомнений в том, что есть ряд других способов достичь этого эффекта, но я настоятельно призываю вас рассмотреть подход, который приводит только к одному экземпляру автомобиля на VIN.
Другие советы
Я бы бросил принцип поцелуя, если это возможно. Если ваш фактический ключ - это идентификатор, а не самого транспортного средства, то почему бы не использовать TDictionary<string, TPerson>
вместо TDictionary<TVehicle, TPerson>
? Тогда вам не придется беспокоиться о пользовательских сравнениях.
Будучи консультированы о запахе в вашем дизайне и других вещах, я отвечу на ваш вопрос, так как он действителен, чтобы создать словарь с ключом объекта и сравнить его на основе чего -либо, отличного от адреса памяти ключа:
Вы можете создать новый сравнитель во время создания 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
При этом, я думаю, что это недостаток в вашем коде, если у вас есть два экземпляра, представляющих один и тот же объект. Если у вас есть Tvehicle с идентификатором «ABC» в памяти, что для меня, это должен быть единственный экземпляр этого автомобиля, и вы должны предоставить какой -то способ получить этот же экземпляр для всего вашего кода. Таким образом, вы можете использовать класс словаря без написания пользовательского сравнения, но что более важно, вы знаете, что работаете все время с одним и тем же объектом, и состояние вашего приложения будет и казаться согласованным для чего -либо в коде, пользовательском интерфейсе или других интерфейсах Анкет