Recent

Author Topic: How to have a generic enumerator of a TObjectList?  (Read 615 times)

CCRDude

  • Hero Member
  • *****
  • Posts: 546
How to have a generic enumerator of a TObjectList?
« on: March 18, 2023, 08:59:53 pm »
I'll sum up my question in pseudocode first:

Code: Pascal  [Select][+][-]
  1. type
  2.    TMyData = class
  3.    published
  4.       property Something: string;
  5.    end;
  6.  
  7.    TMyList = class(TObjectList<TMyData>)
  8.    end;
  9.  
  10.    TListViewHelper = class helper for TListview
  11.    public
  12.       procedure HandleAllItemsInTMyList(WhatComesHere?);
  13.    end;
  14.  

To be more detailed: i want to pass any TObjectList to a method of that class helper, which will then display all objects inside the listview.

I foreach the entries in TMyList in the calling code and display each one from there easily, but I want to pass a TObjectList<any> parameter, or a TEnumerator<any> parameter... but I'm not that good at generics to know how.

Any ideas?

Blaazen

  • Hero Member
  • *****
  • Posts: 3150
  • POKE 54296,15
    • Eye-Candy Controls
Re: How to have a generic enumerator of a TObjectList?
« Reply #1 on: March 18, 2023, 09:48:53 pm »
WhatComesHere? can be for example ALiist: TFPSList in case you use TFPGObjectList from FGL.
But still, you will have to distinguish what type specializes the list (i.e. TMyData or something else).

Or - all types that specializes the lists will have common ancestor with some virtual method that wil allow you to handle all lists.
Lazarus 2.3.0 (rev main-2_3-2863...) FPC 3.3.1 x86_64-linux-qt Chakra, Qt 4.8.7/5.13.2, Plasma 5.17.3
Lazarus 1.8.2 r57369 FPC 3.0.4 i386-win32-win32/win64 Wine 3.21

Try Eye-Candy Controls: https://sourceforge.net/projects/eccontrols/files/

CCRDude

  • Hero Member
  • *****
  • Posts: 546
Re: How to have a generic enumerator of a TObjectList?
« Reply #2 on: March 18, 2023, 10:58:16 pm »
I use Generics.Collections instead of FGL.

And my point is that I don't want to specialize the list. I want any variant of the list, since the code inside use RTTI to convert it for display as TListItems.

I thought something like the enumerator could replace the need for a virtual method.


Warfley

  • Hero Member
  • *****
  • Posts: 1068
Re: How to have a generic enumerator of a TObjectList?
« Reply #3 on: March 19, 2023, 12:16:45 am »
Something like that?
Code: Pascal  [Select][+][-]
  1. program Project1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. uses
  6.   classes, Generics.Collections;
  7.  
  8. generic procedure EnumerateAndDoStuff<T>(items: specialize TEnumerable<T>);
  9. var
  10.   item: T;
  11. begin
  12.   for item in items do
  13.     WriteLn(item.Text);
  14. end;
  15.  
  16. var
  17.   lst: specialize TObjectList<TStringList>;
  18. begin
  19.   lst := specialize TObjectList<TStringList>.Create;
  20.   try
  21.     lst[lst.Add(TStringList.Create)].Text := 'Hello';
  22.     lst[lst.Add(TStringList.Create)].Text := 'World';
  23.     specialize EnumerateAndDoStuff<TStringList>(lst);
  24.   finally
  25.     lst.Free;
  26.   end;
  27. end.

jamie

  • Hero Member
  • *****
  • Posts: 5160
Re: How to have a generic enumerator of a TObjectList?
« Reply #4 on: March 19, 2023, 12:22:45 am »
TCollection ?

It holds all the information about the class and object.
The only true wisdom is knowing you know nothing

CCRDude

  • Hero Member
  • *****
  • Posts: 546
Re: How to have a generic enumerator of a TObjectList?
« Reply #5 on: March 19, 2023, 08:12:49 pm »
Or - all types that specializes the lists will have common ancestor with some virtual method that wil allow you to handle all lists.

That is the thing I was looking for.

Something like that?

Thank you! I use Delphi mode, but must admit I didn’t know the correct syntax for generic methods. So thanks for pointing me at it, it now allows me to:

It’s a bit complex since it means I need to call this method with a really long line:

Code: [Select]
   lv.DisplayEnumerable<TDemoObjectList>(TEnumerable<TDemoObjectList>(FList));

Obviously, I would prefer something to just call lv.DisplayItems(FList) instead, but this is a good start!

See line 1179 here and line 75 here.


TCollection? It holds all the information about the class and object.

The essential part is that I want this method to work for as many list types as possible :)
But thanks for hinting me at TCollection, that one is indeed easy to implement.

...

Maybe I could use RTTI here as well, to check if the provided class has the properties Count and Items, and operate on what the Items Getter returns. But these two properties are only public, not published, so GetPropList won't work for that approach.

Warfley

  • Hero Member
  • *****
  • Posts: 1068
Re: How to have a generic enumerator of a TObjectList?
« Reply #6 on: March 19, 2023, 08:57:15 pm »
You probably don't need the explicit cast, so it should work like this:
Code: Pascal  [Select][+][-]
  1. lv.DisplayEnumerable<TDemoObjectList>(FList);
With the next fpc version there will also be implicit specialization which let's you reduce it to
Code: Pascal  [Select][+][-]
  1. lv.DisplayEnumerable(FList);

CCRDude

  • Hero Member
  • *****
  • Posts: 546
Re: How to have a generic enumerator of a TObjectList?
« Reply #7 on: Today at 08:55:34 am »
If course I tried that, because TDemoObjectList = class(TObjectList<TDemoObjectListItem>) inherits from TEnumerable<T>.

But I get the following:

Quote
OVMDemo.UI.FormMain.pas(76,53) Error: Incompatible type for arg no. 1: Got "TDemoObjectList", expected "TEnumerable<OVMDemo.DemoData.TDemoObjectList>"

As an alternative, I'm looking into simply passing the class helper a TObject and reacting properly in there, see line 1197ff:

Code: Pascal  [Select][+][-]
  1. procedure TObjectListViewClassHelper.DisplayItems(AList: TObject);
  2.  
  3.    procedure DisplayCollection(ACollection: TCollection);
  4.    var
  5.       ci: TCollectionItem;
  6.       li: TListItem;
  7.    begin
  8.       for ci in ACollection do begin
  9.          li := Self.Items.Add;
  10.          Self.UpdateListItemForObject(li, ci);
  11.       end;
  12.    end;
  13.  
  14. begin
  15.    Self.Items.BeginUpdate;
  16.    try
  17.       Self.Items.Clear;
  18.       Self.Columns.Clear;
  19.       if AList is TCollection then begin
  20.          DisplayCollection(AList as TCollection);
  21.       end else begin
  22.          // it would be great if generic collections could be detected here
  23.          OutputDebugString(PChar(string(AList.ClassParent.ClassName)));
  24.          // test 1: detect class
  25.          //if AList.ClassParent.ClassName is TObjectList then begin
  26.             // fails because is cannot be used here
  27.          //end;
  28.          // test 2: rtti? won't work because public, not published
  29.          //property Count: SizeInt read FLength write SetCount;
  30.          //property Items[Index: SizeInt]: T read GetItem write SetItem; default;
  31.       end;
  32.    finally
  33.       Self.Items.EndUpdate;
  34.    end;
  35. end;  

But it seems that is does not work on generics without specialization:
Quote
OVM.ListView.pas(1281,44) Error: Generics without specialization cannot be used as a type for a variable

Problem is that I want to work on any, without specialization ;)

Am I missing the right syntax, or is it not possible to "detect" the abstract generics type of a type based on generics?
I could use ClassParent.ClassName to detect that the parent class is TObjectList<something>, but still would not know how to access properties.

At this stage, it's mostly interest, since it's now working and I published this package mostly because I want to publish a larger one where this is used in an example. And I like to improve things ;)

(PS: FreePascal 3.3.1-1245 from 2023/02/06... will update my trunk environmen asap)

CCRDude

  • Hero Member
  • *****
  • Posts: 546
Re: How to have a generic enumerator of a TObjectList?
« Reply #8 on: Today at 09:05:03 am »
I was able to trigger a compiler exception:

Quote
OVM.ListView.pas(1285,55) Error: Compilation raised exception internally
Verbose: Compilation aborted
Debug: An unhandled exception occurred at $0042057E:
Debug: EListError: List index exceeds bounds (0)

Code: Pascal  [Select][+][-]
  1. begin
  2.    Self.Items.BeginUpdate;
  3.    try
  4.       Self.Items.Clear;
  5.       Self.Columns.Clear;
  6.       if AList is TCollection then begin
  7.          DisplayCollection(AList as TCollection);
  8.       end else begin
  9.          OutputDebugString(PChar(string(AList.ClassParent.ClassName)));
  10.          if SameText('TObjectList<', Copy(AList.ClassParent.ClassName, 1, 12)) then begin
  11.             // Identifier not found for the below, even though it's part of
  12.             // the same class helpe and can be called from main code
  13.             // Self.DisplayEnumerable<AList.ClassType>(Self, TEnumerable<AList.ClassType>(AList));
  14.  
  15.             // the below triggers:
  16.             //   OVM.ListView.pas(1285,55) Error: Compilation raised exception internally
  17.             //   Verbose: Compilation aborted
  18.             //   Debug: An unhandled exception occurred at $0042057E:
  19.             //   Debug: EListError: List index exceeds bounds (0)
  20.             FillListViewFromObjectList<AList.ClassType>(Self, TEnumerable<AList.ClassType>(AList));
  21.             // FillListViewFromObjectList is the same as the above DisplayEnumerable, except as a stand-alone procedure instead of the class helpers method.
  22.          end;
  23.       end;
  24.    finally
  25.       Self.Items.EndUpdate;
  26.    end;
  27. end;

(update still raises exception with 3.3.1-12575 from 2023-03-19)
« Last Edit: Today at 09:58:08 am by CCRDude »

 

TinyPortal © 2005-2018