В Delphi 12.3 Athens появилась ошибка компиляции, связанная с использованием общих шаблонов (generics) и управляемых записей (managed records). Ошибка проявляется как "[dcc32 Error] Type parameter 'T' must be a non-nullable value type", и возникает, когда общий шаблон с ограничением : record используется с записью, содержащей управляемые типы данных, такие как string, динамические массивы или пользовательские атрибуты.
Суть проблемы:
Ранее, в версиях Delphi до 12.3, компилятор допускал использование управляемых записей в общих шаблонах с ограничением : record. Однако, в Delphi 12.3 это поведение было изменено, и теперь компилятор выдает ошибку. Официальная позиция Embarcadero заключается в том, что это изменение "as designed" (задумано так) и связано с предотвращением ошибок, которые могли возникать из-за особенностей управления памятью управляемых записей.
Пример кода, вызывающего ошибку:
unit UTestGenericsBug;
interface
uses
System.Rtti,
Data.DB;
type
DBField = class(TCustomAttribute)
strict private
FFieldType: TFieldType;
FLength: Integer;
public
constructor Create(const AFieldType: TFieldType = ftUnknown; const ALength: Integer = 0);
property FieldType: TFieldType read FFieldType;
property Length: Integer read FLength;
end;
TTestRecord = record
private
[DBField(ftString, 20)]
FTerminalName: string;
public
end;
TRecordLoader<T: record> = class sealed
class function Get: T; static;
end;
implementation
constructor DBField.Create(const AFieldType: TFieldType = ftUnknown; const ALength: Integer = 0);
begin
end;
class function TRecordLoader<T>.Get: T;
begin
Result := Default(T);
end;
function Get: TTestRecord;
begin
// Ошибка компиляции здесь
Result := TRecordLoader<TTestRecord>.Get;
end;
end.
В данном примере, запись TTestRecord содержит поле FTerminalName типа string и атрибут DBField. При попытке использовать TTestRecord в качестве параметра типа T для общего класса TRecordLoader<T: record>, компилятор выдает ошибку.
Решение (Workaround):
Предложенное решение заключается в явном указании полного объявления общего типа при использовании класса TRecordLoader. Вместо:
Result := TRecordLoader<TTestRecord>.Get;
Следует использовать:
Result := TRecordLoader<TTestRecord>.Get<TTestRecord>;
Это позволяет компилятору корректно определить тип параметра и избежать ошибки. Однако, как отмечают пользователи, такое решение нивелирует преимущество использования ограничения : record, поскольку теперь общий шаблон может принимать типы, для которых он не был предназначен.
Альтернативные решения и последствия:
Использование записей без управляемых типов: Можно перепроектировать записи таким образом, чтобы они не содержали управляемые типы данных, такие как string, динамические массивы и т.д. Вместо string можно использовать ShortString (например, string[255]), но это может привести к проблемам совместимости и необходимости конвертации данных. Как было отмечено в обсуждении, это также может привести к предупреждениям компилятора W1057 "Implicit string cast from 'ShortString' to 'string'".
TTestRecord = record private
[DBField(ftString, 20)] FTerminalName: string[20]; // Использование ShortString
public
end;
Недостатком этого подхода является ограничение максимальной длины строки и потенциальные проблемы с кодировкой (ShortString использует кодировку ANSI).
Использование классов вместо записей: Можно заменить запись классом. Однако, это может потребовать значительной переработки кода и повлиять на производительность, поскольку работа с классами требует выделения памяти в куче и управления ею.
Остаться на Delphi 12.2 или более ранней версии: Если изменение поведения компилятора в Delphi 12.3 критично для вашего проекта, можно рассмотреть возможность остаться на предыдущей версии Delphi. Однако, это означает, что вы не сможете воспользоваться новыми функциями и исправлениями ошибок, доступными в Delphi 12.3.
Использовать TValue: Можно использовать TValue для передачи значений, что позволяет обойти ограничение на типы данных. Однако, это может потребовать дополнительной работы по упаковке и распаковке значений.
Вывод:
Изменение поведения компилятора в Delphi 12.3, касающееся использования управляемых записей в общих шаблонах с ограничением : record, представляет собой серьезную проблему для разработчиков, использующих эти возможности. Предложенное решение с явным указанием типа в общих шаблонах является лишь обходным путем, который не решает проблему в полной мере. Альтернативные решения требуют значительной переработки кода и могут повлиять на производительность и совместимость. В данной ситуации, разработчикам следует тщательно оценить все возможные варианты и выбрать наиболее подходящий для их конкретного проекта. Также, стоит обратиться в Embarcadero с запросом о пересмотре данного изменения в будущих версиях Delphi.
В Delphi 12.3 Athens возникла ошибка компиляции при использовании управляемых записей в общих шаблонах с ограничением `: record`, что требует обходных решений или переработки кода.
Комментарии и вопросы
Получайте свежие новости и обновления по Object Pascal, Delphi и Lazarus прямо в свой смартфон. Подпишитесь на наш Telegram-канал delphi_kansoftware и будьте в курсе последних тенденций в разработке под Linux, Windows, Android и iOS
Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.