Recent

Author Topic: [solved] How to have a generic enumerator of a TObjectList?  (Read 1342 times)

CCRDude

  • Hero Member
  • *****
  • Posts: 554
[solved] 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?
« Last Edit: March 23, 2023, 08:45:43 am by CCRDude »

Blaazen

  • Hero Member
  • *****
  • Posts: 3234
  • 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: 554
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: 1215
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: 5362
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: 554
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: 1215
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: 554
Re: How to have a generic enumerator of a TObjectList?
« Reply #7 on: March 20, 2023, 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: 554
Re: How to have a generic enumerator of a TObjectList?
« Reply #8 on: March 20, 2023, 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: March 20, 2023, 09:58:08 am by CCRDude »

Stefan Glienke

  • New Member
  • *
  • Posts: 16
Re: How to have a generic enumerator of a TObjectList?
« Reply #9 on: March 22, 2023, 06:09:13 pm »
procedure HandleAllItemsInTMyList(items: TEnumerable<TObject>);

Since FPC does not support covariance (afaik) we have to use a hardcast here - any TObjectList<T> only contains objects and inherits from TEnumerable<T> so it can be treated as TEnumerable<TObject>.

CCRDude

  • Hero Member
  • *****
  • Posts: 554
Re: How to have a generic enumerator of a TObjectList?
« Reply #10 on: March 22, 2023, 08:30:00 pm »
Thanks for the new idea!

That one doesn't allow to simply pass the object either though.

Quote
OVMDemo.UI.FormMain.pas(88,30) Error: Incompatible type for arg no. 1: Got "TDemoObjectList", expected "TEnumerable<System.TObject>"

I tried multiple versions, and as soon as I use TObject instead of the generic T, I get incompatible types.

PascalDragon

  • Hero Member
  • *****
  • Posts: 5061
  • Compiler Developer
Re: How to have a generic enumerator of a TObjectList?
« Reply #11 on: March 22, 2023, 11:07:46 pm »
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>"

Judging from the error message wouldn't you have to specialize DisplayEnumerable<> with TDemoObjectListItem instead of TDemoObjectList?

Code: Pascal  [Select][+][-]
  1. ls.DisplayEnumerable<TDemoObjectListItem>(FList)

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)

The type parameters for a generic must be known at compile time. You can not use something like SomeClassType.ClassType (though the compiler should not crash, so that might be worth a bug report with a small example).

Since FPC does not support covariance (afaik) we have to use a hardcast here - any TObjectList<T> only contains objects and inherits from TEnumerable<T> so it can be treated as TEnumerable<TObject>.

FPC supports result type covariance, but also only for the whole type and not the generic parameter types.

CCRDude

  • Hero Member
  • *****
  • Posts: 554
Re: How to have a generic enumerator of a TObjectList?
« Reply #12 on: March 23, 2023, 08:45:26 am »
Judging from the error message wouldn't you have to specialize DisplayEnumerable<> with TDemoObjectListItem instead of TDemoObjectList?
Absolutely! Thanks for pointing me at this :)

The type parameters for a generic must be known at compile time. You can not use something like SomeClassType.ClassType

They are known where the generic instance is defined (here in the demo data unit).
I was hoping that they could still be passed "generically".

The old TObjectList without generics that could store any kind of object would actually be more flexible here.

But thanks to you finding my mistake above, I have an easy to use way to call DisplayEnumerable or DisplayColleciton for quite a lot of types, no need for my DisplayItems(X: TObject) any more :)

(though the compiler should not crash, so that might be worth a bug report with a small example).

Will do!

PascalDragon

  • Hero Member
  • *****
  • Posts: 5061
  • Compiler Developer
Re: How to have a generic enumerator of a TObjectList?
« Reply #13 on: March 23, 2023, 09:45:44 pm »
The type parameters for a generic must be known at compile time. You can not use something like SomeClassType.ClassType

They are known where the generic instance is defined (here in the demo data unit).
I was hoping that they could still be passed "generically".

It is not known here however:

Code: Pascal  [Select][+][-]
  1. FillListViewFromObjectList<AList.ClassType>(Self, TEnumerable<AList.ClassType>(AList));

Neither AList nor AList.ClassType are compile time constants (and more importantly they are not types), thus AList.ClassType can not be used as a generic type parameter.

CCRDude

  • Hero Member
  • *****
  • Posts: 554
Re: How to have a generic enumerator of a TObjectList?
« Reply #14 on: March 25, 2023, 11:13:07 am »
It is not known here however:

Code: Pascal  [Select][+][-]
  1. FillListViewFromObjectList<AList.ClassType>(Self, TEnumerable<AList.ClassType>(AList));

Neither AList nor AList.ClassType are compile time constants (and more importantly they are not types), thus AList.ClassType can not be used as a generic type parameter.

Already admitted that this was a mistake by me :)

I was hoping to use it in DisplayItems(TheItems: TObject) to identify with TheItems is TObjectList (without specialization).
But with my mistake above found, I can simply use overload for multiple supported data types and an error about unsupported ones on compile time even.

 

TinyPortal © 2005-2018