#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 не является допустимым ограничением». Итак, это, кажется, минимальное решение.