如何在Delphi中纯粹通过RTTI信息(即不使用任何实际对象实例)获取TObjectList <t>的子项类型?</t>

时间:2015-03-08 01:21:58

标签: delphi generics rtti tobjectlist

我正在使用RTTI实现用于流式传输任意Delphi对象的通用代码,并且为了使其工作(更具体地说,为了使加载部分工作),我需要以某种方式获得子项类型不使用任何实际对象实例的TObjectList<T>字段。

要求不使用任何实际对象实例的明显原因是,在从流加载对象的情况下(仅基于要加载的对象的类类型的知识),我不会在加载完成之前有任何实例可用 - 我宁愿只能访问相关类的纯RTTI数据。

我希望能够加载的这类课程的示例如下:

TTestClass = class(TObject)
public
   test_list : TList<string>;
end;

我想要的是能够得出结论:test_list字段是TList<T>,其中Tstring(即为了知道预期的数据)来自子项的流)。

如果该课程的内容如下:

TTestClassWithArr = class(TObject)
public
   test_arr : array of string;
end;

我可以使用ElementType()字段的TRttiDynamicArrayType RTTI类的test_arr方法纯粹通过RTTI提取此信息,但我找不到任何相应的此类显式RTTI类型{ {1}}。

另一个Stack Overflow问题(Delphi Rtti: how to get objects from TObjectList<T>)是相关的,但确实使用RTTI数据反映的对象的实际实例来“欺骗”以获取子项,这又是对我来说不是一个选择,因为在我必须知道的时候这些子项目不存在。

真的感觉应该通过单独使用类的RTTI信息来实现这一点,因为所有类型信息显然都是在编译时为它提供的,无论对象实例化如何。

2 个答案:

答案 0 :(得分:3)

不幸的是,没有为通用参数生成RTTI。在T等通用容器中发现TList<T>值的唯一方法是获取目标字段本身的TRttiType,调用其ToString()方法获取其类将name命名为字符串,并解析括号之间的子字符串。例如:

uses
  ..., System.StrUtils, System.Rtti;

var
  Ctx: TRttiContext;
  s: string;
  OpenBracket, CloseBracket: Integer;
  ...
begin
  ...
  s := Ctx.GetType(TTestClass).GetField('test_list').FieldType.ToString; // returns 'TList<System.string>'
  OpenBracket := Pos('<', s);
  CloseBracket := PosEx('>', s, OpenBracket+1);
  s := Copy(s, OpenBracket+1, CloseBracket-OpenBracket-1); // returns 'System.string'
  // if multiple Generic parameters are present, they will be separated by commas...
  ...
end;

将Generic参数解压缩为字符串后,如果需要访问该类型的RTTI,则可以使用TRttiContext.FindType()

话虽如此,下面的代码提供了一堆RTTI助手:

DSharp.Core.Reflection.pas(Google代码)

DSharp.Core.Reflection.pas(BitBucket)

除此之外,它还定义了一个TRttiTypeHelper类助手,可以将GetGenericArguments()方法添加到TRttiType

TRttiTypeHelper = class helper for TRttiType
...
public 
  ...
  function GetGenericArguments: TArray<TRttiType>;
  ...
end;

在内部,GetGenericArguments()使用了我上面提到的相同技术。有了它,你可以这样做:

uses
  ..., System.Rtti, DSharp.Core.Reflection;

var
  Ctx: TRttiContext;
  arr: TArray<TRttiType>;
  typ: TRttiType;
  s: string;
  ...
begin
  ...
  arr := Ctx.GetType(TTestClass).GetField('test_list').FieldType.GetGenericArguments;
  typ := arr[0]; // returns RTTI for System.String
  s := typ.ToString; // returns 'System.string'
  ...
end;

答案 1 :(得分:2)

我一直在寻找解决方案,我有一个想与你分享的想法。该解决方案使用rtti并从列表(TList,TObjectList等)获取方法的参数“Add”。在我的函数中,我只返回类类型,但您可以轻松地实现到primivite类型。我希望它可以帮助别人。问候。

folow中:

class function TUtilRtti.GetSubTypeItemClassFromList(ObjectList: TObject): TClass;
var
  ctxRtti  : TRttiContext;
  typeRtti : TRttiType;
  atrbRtti : TCustomAttribute;
  methodRtti: TRttiMethod;
  parameterRtti: TRttiParameter;
begin
  result := nil;

  ctxRtti  := TRttiContext.Create;
  typeRtti := ctxRtti.GetType( ObjectList.ClassType );
  methodRtti := typeRtti.GetMethod('Add');
  for parameterRtti in methodRtti.GetParameters do
  begin
    if SameText(parameterRtti.Name,'Value') then
    begin
      if parameterRtti.ParamType.IsInstance then
        result := parameterRtti.ParamType.AsInstance.MetaclassType;
      break;
    end;
  end;
  ctxRtti.Free;
end;

样品

...
var
  List: TList<TCustomer>;
begin
  List := TList<Customer>.Create();

  ShowMessage(TUtilRtti.GetSubTypeItemClassFromList(List).ClassName);
end;