Delphi: Как установить значение универсального поля с помощью RTTI?

#delphi #generics #delphi-2010 #rtti

#delphi #общие #delphi-2010 #rtti

Вопрос:

Я хотел бы заполнить поле универсального объекта во время выполнения, используя D2010.

 program generic_rtti_1;
{$APPTYPE CONSOLE}
uses
  SysUtils, rtti;
type
  TMyObject = class
    FField1: string;
  end;
  TGeneric<TElement: class> = class
    procedure FillFields(Element: TElement);
  end;
procedure TGeneric<TElement>.FillFields(Element: TElement);
var
  ctx:  TRttiContext;
begin
  ctx := TRttiContext.Create();
  ctx.GetType(TypeInfo(TElement)).GetField('FField1').
    SetValue(@Element, TValue.FromVariant('Some string'));
  ctx.Free();
end;
  

Когда строка ctx.Free(); выполняется, я получаю AV в строке 21986 в System.pas (функция _IntfClear()). Это вызывается из FContextToken := nil в rtti.pas. (На самом деле, SetValue вызванный AV всплывает, если я перехожу в SetValue , однако, если перешагнуть через него, сообщается только о ctx.Free вызванном. Смотрите ниже.)

Если я удалю ctx.Free(); , при вызове появится AV SetValue(@Element, TValue.FromVariant('Some string')); . Это тоже в строке 21986 в System.pas.

Пытаясь разобраться в этом беспорядке, я заменил

 ctx.GetType(TypeInfo(TElement)).GetField('FField1').
  SetValue(@Element, TValue.FromVariant('Field 1 is set'));
  

с помощью этого:

 rType := ctx.GetType(TypeInfo(TElement));
rField := rType.GetField('FField1');
Val := TValue.FromVariant('Field 1 is set');
rField.SetValue(@Element, Val);
  

На этот раз я не получил ошибки, однако WriteLn(MyObject.FField1) напечатал пустую строку. (AV снова появляется, если я объединяю SetValue и TValue.FromVariant , т.е. записываю rField.SetValue(@Element, TValue.FromVariant('Field 1 is set')); .

Чтобы точно определить виновную строку, я закомментировал построчно, заменив прокомментированный код составным оператором. Случайно я забыл закомментировать Val := TValue.FromVariant('Field 1 is set'); строку выше, из-за чего AV снова исчезает (все еще вызывается rField.SetValue(@Element, TValue.FromVariant('Field 1 is set')); ). (Обратите внимание, что я на самом деле не использую Val в проблемном вызове, все равно AV исчезает.)

На данный момент я немного потерялся.

Для полноты картины, вот как я хотел бы использовать приведенный выше код:

 var
  Generic:  TGeneric<TMyObject>;
  MyObject: TMyObject;
begin
  MyObject := TMyObject.Create();
  Generic := TGeneric<TMyObject>.Create();
  Generic.FillFields();
  WriteLn(MyObject.FField1);
  Generic.Free();
  MyObject.Free();
  ReadLn;
end;
end.
  

Кто-нибудь знает, что я делаю не так? (Возможно ли это вообще? Есть ли лучшие способы сделать это с использованием generics? )

Ответ №1:

Ну, я не знаю, имеет ли это смысл для вас, ребята, но вот как я это решил. Жесткое приведение к TObject в procedure TGeneric<TElement>.FillFields работает как шарм. Вот так:

 ctx.GetType(TypeInfo(TElement)).GetField('FField1').
  SetValue(TObject(Element), TValue.FromVariant('Field 1 is set'));
  

Надеюсь, это пригодится кому-то еще.

Комментарии:

1. Конечно, это имеет смысл, поскольку setValue принимает указатель, а объект является указателем. Я думаю, вы даже можете обойтись без жесткого преобразования, если у вас есть ограничение класса для вашего универсального типа.

2. @StefanGlienke Ну, я действительно передал адрес Element в свой первый заход. Между тем, ограничение было class все время. Компилятор не уловил это, поэтому мне пришлось начать экспериментировать. Итак, ограничение class в основном означает TObject — верно? Если вы попытаетесь ограничить свои общие файлы TObject , вам вежливо скажут, что «TObject не является допустимым ограничением». Итак, это, кажется, минимальное решение.