При работе с записями (records) и обобщенными списками TList в Delphi разработчики часто сталкиваются с ошибкой компиляции E2064 "Left side cannot be assigned to". Эта ошибка возникает при попытке изменить поле записи, полученной через индексатор списка. В данной статье мы подробно разберем причины этой ошибки и предложим несколько способов ее решения.
Проблема и ее причины
Рассмотрим исходный код, который вызывает ошибку:
type
tModel = record
Field: string;
Control: TWinControl;
Kind: integer;
end;
tModels = class
private
FList: TList<tModel>;
public
procedure DoSomething;
// ...
end;
procedure tModels.DoSomething;
begin
FList[1].Field := 'something'; // Ошибка E2064: Left side cannot be assigned to
end;
Почему возникает ошибка?
Когда вы обращаетесь к элементу списка через индексатор (FList[1]), компилятор возвращает копию записи, а не ссылку на оригинал.
Изменение поля временной копии записи не имеет смысла, так как изменения не сохранятся в списке.
Компилятор защищает вас от этой логической ошибки, выдавая сообщение E2064.
Решение 1: Использование свойства List
Первый способ решения проблемы - использование свойства List класса TList<T>, которое предоставляет доступ к внутреннему массиву:
procedure tModels.DoSomething;
begin
FList.List[1].Field := 'something'; // Теперь работает
end;
Преимущества:
- Простота использования
- Не требует изменения структуры кода
Недостатки:
- Нарушает инкапсуляцию, предоставляя прямой доступ к внутренней реализации
- Менее интуитивно понятно для других разработчиков
Решение 2: Использование указателей на записи
Более элегантное решение предложил Anders Melander - использование указателей на записи:
type
TModel = record
Field: string;
Control: TWinControl;
Kind: integer;
end;
PModel = ^TModel;
TModels = class
private
FItems: TList<TModel>;
private
function GetModel(Index: integer): TModel;
procedure SetModel(Index: integer; const Value: TModel);
function GetData(Index: integer): PModel;
public
property Items[Index: integer]: TModel read GetModel write SetModel; default;
property Data[Index: integer]: PModel read GetData;
end;
function TModels.GetModel(Index: integer): TModel;
begin
Result := FItems[Index];
end;
procedure TModels.SetModel(Index: integer; const Value: TModel);
begin
FItems[Index] := Value;
end;
function TModels.GetData(Index: integer): PModel;
begin
Result := @FItems[Index];
end;
Использование:
var
Models: TModels;
begin
// Инициализация Models...
// Получение копии записи
var ModelCopy := Models[0];
// Запись новой копии
Models[0] := ModelCopy;
// Изменение полей напрямую
Models.Data[0].Field := 'Hello';
Models.Data[0].Kind := 42;
end;
Преимущества:
- Сохраняет инкапсуляцию
- Предоставляет два способа работы с записями: по значению и по указателю
- Более чистый интерфейс
Недостатки:
- Требует больше кода для реализации
- Работа с указателями может быть менее безопасной
Решение 3: Замена TList на динамический массив
Как упомянул автор вопроса, можно использовать динамический массив вместо TList:
type
TModel = record
Field: string;
Control: TWinControl;
Kind: integer;
end;
TModelArray = TArray<TModel>;
TModels = class
private
FItems: TModelArray;
public
property Items: TModelArray read FItems write FItems;
end;
Использование:
var
Models: TModels;
begin
// Инициализация Models...
Models.Items[0].Field := 'Test'; // Теперь работает
end;
Преимущества:
- Более простой синтаксис
- Прямой доступ к элементам массива
Недостатки:
- Меньше функциональности по сравнению с TList
- Необходимо самостоятельно управлять размером массива
Решение 4: Создание методов-оберток
Еще один подход - создание методов для изменения отдельных полей:
type
TModels = class
private
FList: TList<TModel>;
public
procedure SetField(Index: Integer; const Value: string);
function GetField(Index: Integer): string;
// Аналогично для других полей
end;
procedure TModels.SetField(Index: Integer; const Value: string);
var
M: TModel;
begin
M := FList[Index];
M.Field := Value;
FList[Index] := M;
end;
function TModels.GetField(Index: Integer): string;
begin
Result := FList[Index].Field;
end;
Преимущества:
- Полный контроль над доступом к полям
- Возможность добавить валидацию
Недостатки:
- Много шаблонного кода
- Менее удобный синтаксис
Сравнение производительности
При выборе решения стоит учитывать производительность:
Использование List: Самый быстрый вариант, но нарушает инкапсуляцию.
Указатели: Незначительные накладные расходы на разыменование.
Динамический массив: Аналогичен List по производительности.
Методы-обертки: Наибольшие накладные расходы из-за создания временных копий.
Рекомендации
Если важна инкапсуляция и безопасность - используйте решение с указателями.
Для максимальной производительности в ущерб инкапсуляции - свойство List.
Для простых случаев с небольшим количеством полей - динамический массив.
Если нужен контроль над изменениями полей - методы-обертки.
Заключение
Ошибка E2064 при работе с записями в TList - это защитный механизм Delphi, предотвращающий неочевидные ошибки. В статье представлены четыре способа решения этой проблемы, каждый со своими преимуществами и недостатками. Выбор оптимального решения зависит от конкретных требований проекта, необходимости сохранения инкапсуляции и вопросов производительности.
Для большинства случаев рекомендуем использовать подход с указателями, как наиболее сбалансированный по соотношению безопасности, производительности и удобства использования.
Ошибка E2064 в Delphi возникает при попытке изменить поле записи, полученной через индексатор списка, и решается через изменение подхода к работе с элементами списка.
Комментарии и вопросы
Получайте свежие новости и обновления по Object Pascal, Delphi и Lazarus прямо в свой смартфон. Подпишитесь на наш Telegram-канал delphi_kansoftware и будьте в курсе последних тенденций в разработке под Linux, Windows, Android и iOS
Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.