I can't figure out how to loop a generic TList instance, do I need to use TValue or the ReinterpretCast trick?

I can't figure out how to loop a generic TList instance, do I need to use TValue or the ReinterpretCast trick?

procedure Foo( AInstance : TObject);
begin
//  how to do here a for loop ?

  if AInstance is a TList  then... ??
    ...
end;

var MyList : TList;
...
Foo(MyList);

Comments

  1. why can't Foo be a method of the list?
    MyList.Foo;

    ReplyDelete
  2. I.e. where you use TList, you rather use TMyList where TMyList have the Foo method.

    ReplyDelete
  3. Using if/then class comparisons is kinda non-OOP.

    ReplyDelete
  4. procedure Foo(AInstance: TObject);
    var
      LList: TList;
      LSomething: TSomething;
    begin
      if AInstance is TList then
      begin
        LList := TList(AInstance);
        for LSomething in LList do
        begin
          //
        end;
      end;
    end;

    Or why have AInstance to be a TObject? This is a lot shorter and easier ;-)
    procedure Foo(AInstance: TList;);
    var
      LSomething: TSomething;
    begin
      for LSomething in LList do
      begin
        //
      end;
    end;

    ReplyDelete
  5. Just enumerate the list after casting. This compiles and works (had a free minute during a download):

    type
      TSomething = class

      end;

    var
      MyList : TList;


    procedure Foo( AInstance : TObject);
    var
      sm: tSomething;
    begin
    //  how to do here a for loop ?

      if AInstance is TList then
      begin
        for sm in TList (AInstance) do
          ShowMessage (sm.ClassName)
      end;

    end;

    procedure TForm1.FormCreate(Sender: TObject);
    begin
      MyList := TList.Create;
      MyList.Add(TSomething.Create);
      Foo(MyList);
    end;

    Bye

    ReplyDelete
  6. The problem is TSomething is declared in a unit that is not accessible from Foo. For non-generic TList its just a "is" and cast TList(Ainstance), but for generic TList the casting works but the "is" does not.

    ReplyDelete
  7. Also Foo is called with different TList types, not only TSomething. (ie: parsing streamed properties that can be of any type)

    ReplyDelete
  8. So you want to enumerate a TListOrTListDescendent? 
    Does these TUnknown have a common ancestor that you will be accessing methods or properties from?

    ReplyDelete
  9. Yes, its a TObject, but "if AInstance is TList" returns false. Casting TList(AInstance) is ok, for any generic TList or non-generic classic TList. I'll try with rtti, maybe there is a way to solve the "is", or Typeinfo comparison.

    ReplyDelete
  10. David Berneda If TSomething is not accessable in Foo then you cannot write if AInstance is TList. To me it sounds as if you need something like covariance here (which is not possible in Delphi).

    If you know for sure that the T in TList is a class then you can write this:

    procedure Foo(AList: TList) 
    var
      obj: TObject
    begin
      for obj in AList do
        ..
    end;

    Foo(TList(MyList));  // hardcast knowing that MyList is a TList where T is a class, otherwise this will possibly crash at runtime.

    For more complex scenarios I suggest taking a look at the Spring4D collections as they provide more flexible ways of handling different types of T (i.e. possibility of getting the type of T at runtime or "cast" them to a non generic list type).

    ReplyDelete
  11. Found a quite satisfaying solution using Rtti and TValue, looking for the public "List" property of generic arrays.

    Thanks to everybody !!

    uses Rtti;

    procedure Foo(AInstance: TObject);
    var
        tmpValue : TValue;
        tmpList  : TRttiProperty;
        tmpContext : TRttiContext;
        tmpType : TRttiType;
        t  : Integer;
    begin
      tmpContext:=TRttiContext.Create;
     try
       tmpType:=tmpContext.GetType( AInstance.ClassInfo );
       tmpList:=tmpType.GetProperty('List');

       if tmpList.PropertyType is TRttiDynamicArrayType then
      begin
         tmpValue:=tmpList.GetValue(AInstance);

         for  t:=0 to tmpValue.GetArrayLength do
                DoSomething( tmpValue.GetArrayElement(t).AsObject );

      end;

    end;

    ReplyDelete
  12. David Berneda Just be aware of the performance hit when using RTTI like that, it's quite slow indeed.

    ReplyDelete
  13. Asbjørn Heid No problem ! Its done when loading a property from a form so its not speed critical.

    ReplyDelete

Post a Comment