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

前端 未结 2 1130
一个人的身影
一个人的身影 2020-12-18 17:05

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

相关标签:
2条回答
  • 2020-12-18 17:57

    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;
    
    0 讨论(0)
  • 2020-12-18 18:06

    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;
    
    0 讨论(0)
提交回复
热议问题