При работе с обобщенными классами в Delphi для чтения и записи значений именованных свойств обобщенного типа часто используют механизмы RTTI. Однако, в некоторых случаях при попытке доступа к свойствам обобщенного типа через RTTI возникают ошибки доступа (EAccessViolation). Рассмотрим пример такой ситуации и возможные способы решения проблемы.
Пример кода, вызывающего ошибку
В представленном ниже коде показана попытка доступа к свойству Item обобщенного класса TTestClass через обобщенный класс TAccessData<T>. При выполнении метода GetTValue и SetTValue возникает исключение EAccessViolation.
program Project1;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils,
System.RTTI;
Type
TTestClass = class
private
FItem: string;
public
Property Item: string read FItem write FItem;
end;
TAccessData<T> = class
Function GetTValue(AItem : T; AField : string) : TValue;
Procedure SetTValue(AItem : T; Afield : string; AValue : TValue);
end;
// Остальная часть реализации TAccessData<T>
// ...
begin
try
LTestObj := TTestClass.Create;
LTestObj.Item := 'Hello';
// Вывод значения свойства Item
Writeln(LTestObj.Item);
LAccessObj := TAccessData<TTestClass>.Create;
AValue := LAccessObj.GetTValue(LTestObj, 'Item');
// Вывод типа значения
Writeln(AValue.TypeInfo^.Name);
if AValue.TypeInfo.Kind <> tkString then
Writeln('Not string');
// Вывод значения свойства, что приводит к EAccessViolation
Writeln(AValue.ToString);
// Установка значения свойства, что также приводит к EAccessViolation
LAccessObj.SetTValue(LTestObj, 'Item', 'World');
// Вывод обновленного значения свойства
Writeln(LTestObj.Item);
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
Readln;
end.
Описание проблемы
Ошибка возникает из-за неправильной передачи указателя на объект в методы TRttiProperty.GetValue и TRttiProperty.SetValue. Для корректной работы с объектами классов необходимо использовать PPointer(@AItem)^ или ограничить обобщенный параметр T типом class, чтобы можно было использовать Pointer(AItem).
Подтвержденный ответ
Для исправления ошибки необходимо изменить передачу параметра AItem в методы GetTValue и SetTValue обобщенного класса TAccessData<T>. Вместо @AItem следует использовать PPointer(@AItem)^ или применить ограничение обобщенного параметра T классом, что позволит использовать Pointer(AItem).
Исправленный код
program Project1;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils,
System.RTTI;
Type
TTestClass = class
private
FItem: string;
public
Property Item: string read FItem write FItem;
end;
TAccessData<T> = class
Function GetTValue(AItem : T) const PPointer : TValue; overload;
Procedure SetTValue(AItem : T) const PPointer; const AField : string; const AValue : TValue; overload;
end;
// Остальная часть реализации TAccessData<T>
// Изменения в функциях GetTValue и SetTValue
// ...
function TAccessData<T>.GetTValue(AInstance : T) const PPointer : TValue;
var
LContext : TRttiContext;
LType : TRttiType;
LProperty : TRttiProperty;
begin
Result := nil;
LType := LContext.GetType(TypeInfo(T));
LProperty := LType.GetProperty(AField);
if LProperty <> nil then
Result := LProperty.GetValue(Pointer(AInstance));
end;
// ...
procedure TAccessData<T>.SetTValue(AInstance : T) const PPointer; const AField : string; const AValue : TValue;
var
LContext : TRttiContext;
LType : TRttiType;
LProperty : TRttiProperty;
begin
LType := LContext.GetType(TypeInfo(T));
LProperty := LType.GetProperty(AField);
if LProperty <> nil then
LProperty.SetValue(Pointer(AInstance), AValue);
end;
// ...
Альтернативный ответ (ограничение обобщенного параметра)
Если необходимо, чтобы обобщенный класс работал как с классами, так и с записями, можно использовать ограничение обобщенного параметра T следующим образом:
TAccessData<T: class, TValueOfT> = class
// ...
end;
Это позволит использовать методы GetTValue и SetTValue, применяя кастинг к Pointer(AItem).
Заключение
При работе с RTTI важно правильно передавать указатели на объекты в соответствующие методы. Исправление кода, представленное выше, позволит избежать ошибок доступа к свойствам обобщенного типа в Delphi.
Вопрос связан с решением проблемы доступа к именованным свойствам обобщенных типов в языке программирования Delphi, используя механизмы RTTI, и возможными способами устранения ошибки `EAccessViolation` при этом доступе.
Комментарии и вопросы
Получайте свежие новости и обновления по Object Pascal, Delphi и Lazarus прямо в свой смартфон. Подпишитесь на наш Telegram-канал delphi_kansoftware и будьте в курсе последних тенденций в разработке под Linux, Windows, Android и iOS
Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.