При работе с записями (record) в Delphi часто возникает необходимость сравнения их содержимого. Класс TEqualityComparer<T> из модуля System.Generics.Defaults предоставляет стандартный способ сравнения, но его поведение с записями, содержащими управляемые типы (такие как String), может быть неожиданным. В этой статье мы разберём причины такого поведения и предложим альтернативные решения.
Сравнение памяти (CompareMem): TEqualityComparer<T>.Default использует побайтовое сравнение памяти, что хорошо работает для простых типов, но не подходит для управляемых типов.
Строки как управляемые типы: При сравнении строк через CompareMem сравниваются не сами символы, а указатели на данные строк.
Копирование в массивы: При помещении записей в массивы создаются копии строк, поэтому указатели становятся разными.
Решения проблемы
1. Реализация собственного компаратора
Самый надёжный способ - создать собственный компаратор:
type
TTestRecComparer = class(TInterfacedObject, IEqualityComparer<TestRec>)
public
function Equals(const Left, Right: TestRec): Boolean; reintroduce;
function GetHashCode(const Value: TestRec): Integer; reintroduce;
end;
function TTestRecComparer.Equals(const Left, Right: TestRec): Boolean;
begin
Result := Left.Value = Right.Value;
end;
function TTestRecComparer.GetHashCode(const Value: TestRec): Integer;
begin
Result := BobJenkinsHash(Pointer(Value.Value)^, Length(Value.Value) * SizeOf(Char), 0);
end;
// Использование:
Comparer := TTestRecComparer.Create;
2. Перегрузка операторов в записи (Delphi 2006+)
Для записей можно перегрузить операторы сравнения:
type
TestRec = record
Value: String;
class operator Equal(const A, B: TestRec): Boolean;
end;
class operator TestRec.Equal(const A, B: TestRec): Boolean;
begin
Result := A.Value = B.Value;
end;
// Теперь можно использовать обычный оператор =:
if Rec1 = Rec2 then
WriteLn('Records are equal');
3. Использование атрибутов (Delphi XE7+)
В новых версиях Delphi можно использовать атрибуты для автоматического создания компаратора:
type
[RecordCompare]
TestRec = record
[Compare]
Value: String;
end;
Альтернативное решение: хеширование строк
Для улучшения производительности при частых сравнениях можно предварительно вычислять хеш:
type
TestRec = record
Value: String;
Hash: Integer;
procedure UpdateHash;
class operator Equal(const A, B: TestRec): Boolean;
end;
procedure TestRec.UpdateHash;
begin
Hash := BobJenkinsHash(Pointer(Value)^, Length(Value) * SizeOf(Char), 0);
end;
class operator TestRec.Equal(const A, B: TestRec): Boolean;
begin
Result := (A.Hash = B.Hash) and (A.Value = B.Value);
end;
Заключение
Стандартный TEqualityComparer<T>.Default не подходит для сравнения записей с управляемыми типами, так как использует побайтовое сравнение памяти. Для корректной работы следует:
Либо реализовать собственный компаратор
Либо перегрузить операторы сравнения в записи
Либо использовать атрибуты в новых версиях Delphi
Выбор конкретного решения зависит от версии Delphi и требований к производительности. Для максимальной гибкости и контроля рекомендуется реализация собственного компаратора.
TEqualityComparer.Default не всегда корректно сравнивает записи с текстовыми полями из-за использования побайтового сравнения памяти, что не подходит для управляемых типов, таких как строки.
Комментарии и вопросы
Получайте свежие новости и обновления по Object Pascal, Delphi и Lazarus прямо в свой смартфон. Подпишитесь на наш Telegram-канал delphi_kansoftware и будьте в курсе последних тенденций в разработке под Linux, Windows, Android и iOS
Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.