Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
838 views
in Technique[技术] by (71.8m points)

generics - 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?

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.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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;

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...