How can I get the sub-item type of a TObjectList<T> purely by RTTI information (i.e. without using any actual object instance) in Delphi?

只愿长相守 提交于 2019-11-29 08:58:10

Unfortunately, there is no RTTI generated for Generic parameters. The only way to discover the value of T in a Generic container like TList<T> is to get the TRttiType for the target field itself, call its ToString() method to get its class name as a string, and parse out the substring that is between the brackets. For example:

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;

Once you have extracted the Generic parameter as a string, you can use TRttiContext.FindType() if you need to access the RTTI for that type.

With that said, the following code provides a bunch of RTTI helpers:

DSharp.Core.Reflection.pas (Google Code)

DSharp.Core.Reflection.pas (BitBucket)

Amongst other things, it defines a TRttiTypeHelper class helper that adds a GetGenericArguments() method to TRttiType:

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

Internally, GetGenericArguments() uses the same technique I mention above. With it, you can do this instead:

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;

I've been looking for a solution about this and I had an ideia that I'd like to share with you. That solution uses rtti and get the method's argument "Add" from a list(TList,TObjectList,etc). In my function I just return the Class Type, but you can easyly implements to primivite types. I hope it can help someone. Regards.

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;

Sample

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

  ShowMessage(TUtilRtti.GetSubTypeItemClassFromList(List).ClassName);
end;
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!