0

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

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

我希望能够加载的此类的一个示例如下:

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

我想要的是能够得出结论,该test_list字段是通用的TList<T>where Tis string(即,为了了解子项的流中期望的数据)。

如果该类确实如下所示:

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

我可以使用字段的RTTI 类的ElementType()方法纯粹通过 RTTI 来提取这些信息,但是我找不到任何对应的这种显式 RTTI 类型。TRttiDynamicArrayTypetest_arrTObjectList<T>

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

确实感觉应该有某种方法可以通过仅使用类的 RTTI 信息来做到这一点,因为无论对象实例化如何,所有类型信息都在编译时显然存在。

4

2 回答 2

3

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

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 参数提取为字符串后,TRttiContext.FindType()如果需要访问该类型的 RTTI,就可以使用。

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

DSharp.Core.Reflection.pas(谷歌代码)

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;
于 2015-03-08T02:56:33.987 回答
3

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

关注:

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;
于 2018-01-29T19:14:47.420 回答