Delphi:如何使用RTTI设置泛型的字段值?

时间:2011-11-10 09:21:44

标签: delphi generics 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();行时,我在System.pas(函数_IntfClear())的第21986行得到一个AV。这是从rtti.pas中的FContextToken := nil调用的。 (事实上​​,如果我进入SetValueSetValue引起的AV会弹出,但如果跳过它,则只会报告ctx.Free - 诱导。请参阅下文。)

如果我删除ctx.Free();,则在调用SetValue(@Element, TValue.FromVariant('Some string'));时会显示AV。这也是在System.pas中的21986行。

试图解决这个问题,我换了

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)打印了一个空字符串。 (如果我合并SetValueTValue.FromVariant,则会重新显示AV,即写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.

有谁知道我做错了什么? (这是否可能?使用泛型有更好的方法吗?)

1 个答案:

答案 0 :(得分:5)

好吧,我不知道这对你们是否有意义,但这就是我解决它的方法。在procedure TGeneric<TElement>.FillFields中强硬转换为TObject就像一个魅力。像这样:

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

希望这对其他人有用。