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 wo
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;
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;