Карта сайта Kansoftware
НОВОСТИУСЛУГИРЕШЕНИЯКОНТАКТЫ
KANSoftWare

Почему TEqualityComparer.Default не всегда корректно сравнивает записи в списках с текстовыми полями.

Delphi , Компоненты и Классы , TEdit

 

Введение

При работе с записями (record) в Delphi часто возникает необходимость сравнения их содержимого. Класс TEqualityComparer<T> из модуля System.Generics.Defaults предоставляет стандартный способ сравнения, но его поведение с записями, содержащими управляемые типы (такие как String), может быть неожиданным. В этой статье мы разберём причины такого поведения и предложим альтернативные решения.

Проблема стандартного сравнения

Рассмотрим пример, демонстрирующий проблему:

type
  TestRec = record
    Value: String;
  end;

var
  Rec1, Rec2: TestRec;
  List1, List2: TArray<TestRec>;
  Comparer: IEqualityComparer<TestRec>;
begin
  Rec1.Value := 'a';
  Rec2.Value := 'a';

  List1 := [Rec1];
  List2 := [Rec2];

  Comparer := TEqualityComparer<TestRec>.Default;

  WriteLn(Comparer.Equals(Rec1, Rec2));       // Выводит True
  WriteLn(Comparer.Equals(List1[0], List2[0])); // Выводит False
end;

Причины неожиданного поведения

  1. Сравнение памяти (CompareMem): TEqualityComparer<T>.Default использует побайтовое сравнение памяти, что хорошо работает для простых типов, но не подходит для управляемых типов.

  2. Строки как управляемые типы: При сравнении строк через CompareMem сравниваются не сами символы, а указатели на данные строк.

  3. Копирование в массивы: При помещении записей в массивы создаются копии строк, поэтому указатели становятся разными.

Решения проблемы

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 не подходит для сравнения записей с управляемыми типами, так как использует побайтовое сравнение памяти. Для корректной работы следует:

  1. Либо реализовать собственный компаратор
  2. Либо перегрузить операторы сравнения в записи
  3. Либо использовать атрибуты в новых версиях Delphi

Выбор конкретного решения зависит от версии Delphi и требований к производительности. Для максимальной гибкости и контроля рекомендуется реализация собственного компаратора.

Создано по материалам из источника по ссылке.

TEqualityComparer.Default не всегда корректно сравнивает записи с текстовыми полями из-за использования побайтового сравнения памяти, что не подходит для управляемых типов, таких как строки.


Комментарии и вопросы

Получайте свежие новости и обновления по Object Pascal, Delphi и Lazarus прямо в свой смартфон. Подпишитесь на наш Telegram-канал delphi_kansoftware и будьте в курсе последних тенденций в разработке под Linux, Windows, Android и iOS




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


:: Главная :: TEdit ::


реклама


©KANSoftWare (разработка программного обеспечения, создание программ, создание интерактивных сайтов), 2007
Top.Mail.Ru

Время компиляции файла: 2024-12-22 20:14:06
2025-06-16 17:04:01/0.0052039623260498/1