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-28 02:19:39

问题


I'm implementing generic code for streaming arbitrary Delphi objects using RTTI, and in order to get this to work (more specifically, in order to get the loading part to work), I need to somehow get the sub-item type of a TObjectList<T> field without making use of any actual object instance.

The obvious reason for the requirement to not use any actual object instance is that in the case of loading an object from a stream (based solely on the knowledge of the class type of the object to be loaded), I won't have any instance at all available before the loading has completed - I will rather only have access to the pure RTTI data of the class in question.

An example of such a class that I'd like to be able to load is the following:

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

What I want is to be able to conclude that the test_list field is a generic TList<T> where T is string (i.e. in order to know what data to expect from the stream for the sub-items).

If the class did instead look as follows:

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

I could use the ElementType() method of the TRttiDynamicArrayType RTTI class of the test_arr field to extract this information purely through RTTI, but I cannot find any corresponding such explicit RTTI type for TObjectList<T>.

Another Stack Overflow question (Delphi Rtti: how to get objects from TObjectList<T>) is related, but does indeed use an actual instance of the object that the RTTI data reflects to "cheat" in order to get to the sub-items, which, again, is not an option for me since these sub-items do not exist at the time I must know this.

It really feels like there should be some way to do this by solely using the RTTI information of the class though, since all the type information is obviously present for it at compile-time, regardless of object instantiation.


回答1:


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;



回答2:


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;


来源:https://stackoverflow.com/questions/28922070/how-can-i-get-the-sub-item-type-of-a-tobjectlistt-purely-by-rtti-information

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