* * *

Author Topic: TCollection question  (Read 1145 times)

PatBayford

  • Jr. Member
  • **
  • Posts: 60
TCollection question
« on: June 19, 2017, 05:27:27 pm »
Since ALL of the TCollection descendants know the class of the items held by the list, why do they not return items of the correct class? I know typecasting is easy enough, but it is time consuming, and it's easy to forget. Does this not destroy a basic tenet of polymorphism?
Is this another left-over from Delphi, where it was a serious problem, mainly due to the seemingly haphazard way that containers were handled?
Lazarus 1.6.4 FPC 3.0.2 SVN 54278 Windows 10 64bit

michalis

  • Jr. Member
  • **
  • Posts: 87
    • Homepage
Re: TCollection question
« Reply #1 on: June 19, 2017, 06:09:28 pm »
The TCollection only "knows" that it holds a TCollectionItem instances. Even if you, as a developer, only insert there "TMyCollectionItemDescendant", the TCollection class does not know it, and also it has no way of ensuring it at compile time. The author of TCollection had no choice but to declare TCollection.Items as a list of TCollectionItem instances. So it returns just TCollectionItem.

This is solved using generics in newer versions. Instead of TCollection, use the generic lists to have various containers (like lists) return the appropriate type. The FPC unit FGL (see reference and wiki ) implements various generic containers, e.g. the simplest generic list is TFPGList.

In FPC 3.1.1 there is also a larger set of units dealing with generic containers (in a Delphi-compatible way, BTW), the main unit is called Generics.Collections. It can be considered an alternative to FGL (it's much better, IMHO, but it's not available in FPC <= 3.0.2 yet).

Another alternative to FGL is inside the "fcl-stl" package, with units like GVector. These have interface inspired by C++ generic containers.

howardpc

  • Hero Member
  • *****
  • Posts: 2251
Re: TCollection question
« Reply #2 on: June 19, 2017, 07:45:36 pm »
Since ALL of the TCollection descendants know the class of the items held by the list, why do they not return items of the correct class?

Not quite sure what you mean.
For example, TStatusBar has a TCollection descendant property Panels (of type TStatusPanels). Its indexed property Items[] returns items of type TStatusPanel (a TCollectionItem descendant), which is of course the correct class. In its implementation the getter method GetItem certainly uses a typecast on the inherited GetItem method. Is this what you mean?
This sort of typecast on base types used in older style collection lists (or the parameterised types used in newer style generic lists) are unavoidable when programming in such a strongly typed languge as Pascal, a language that by design saves us from accidental buffer overruns and other such horrors.

hnb

  • Full Member
  • ***
  • Posts: 195
Re: TCollection question
« Reply #3 on: June 19, 2017, 08:49:23 pm »
...In FPC 3.1.1 there is also a larger set of units dealing with generic containers (in a Delphi-compatible way, BTW), the main unit is called Generics.Collections. It can be considered an alternative to FGL (it's much better, IMHO, but it's not available in FPC <= 3.0.2 yet)...

generally https://github.com/dathox/generics.collections should work for 3.0.2 (mirror for the rtl-generics). Anyway this may change in near future.
Checkout NewPascal initiative and donate beer - ready to use tuned FPC compiler + Lazarus for mORMot project

best regards,
Maciej Izak

michalis

  • Jr. Member
  • **
  • Posts: 87
    • Homepage
Re: TCollection question
« Reply #4 on: June 19, 2017, 09:30:16 pm »
...In FPC 3.1.1 there is also a larger set of units dealing with generic containers (in a Delphi-compatible way, BTW), the main unit is called Generics.Collections. It can be considered an alternative to FGL (it's much better, IMHO, but it's not available in FPC <= 3.0.2 yet)...

generally https://github.com/dathox/generics.collections should work for 3.0.2 (mirror for the rtl-generics). Anyway this may change in near future.

Great! I want to migrate Castle Game Engine to use Generics.Collections soon (so far we use FGL, but I want to bring Delphi compatibility in the next CGE version, so it will be best to switch to Generics.Collections as that's compatible with Delphi). So, I will want to temporarily (until we're all happily using FPC 3.2.0 :) ) maintain a Generics.Collections version within CGE, for usage with older FPC.

PatBayford

  • Jr. Member
  • **
  • Posts: 60
Re: TCollection question
« Reply #5 on: June 19, 2017, 11:10:10 pm »
Yes Howard, I take your point about those TCollections maintained by the libraries, however, when you start sub-classing TCollection in programs, you have to declare an object reference as a TCollectionItem to avoid a compiler error message, and then typecast that reference to the correct type. Strikes me as a bit odd given that the TCollection holds the class of the listed objects as a property!
(Actually, you CAN avoid the compiler errors by using a TypeClass declaration, but you still have to type cast the objects)
Thanks for the info on Generics Michalis - will look at those as and when Lazarus implements FPC 3.2.0!
 
Lazarus 1.6.4 FPC 3.0.2 SVN 54278 Windows 10 64bit

howardpc

  • Hero Member
  • *****
  • Posts: 2251
Re: TCollection question
« Reply #6 on: June 19, 2017, 11:52:39 pm »
Can you show a compilable code example of what you mean?
Perhaps it can be improved.

PatBayford

  • Jr. Member
  • **
  • Posts: 60
Re: TCollection question
« Reply #7 on: June 20, 2017, 02:14:39 am »
Can you show a compilable code example of what you mean?
Perhaps it can be improved.
Attached is a (very) simple project illustrating the problem.
Yes, I know it uses TObjectList, rather than a TCollection BUT the broad principles are similar.
Lazarus 1.6.4 FPC 3.0.2 SVN 54278 Windows 10 64bit

howardpc

  • Hero Member
  • *****
  • Posts: 2251
Re: TCollection question
« Reply #8 on: June 20, 2017, 12:05:43 pm »
TObjectList's default enumerator is the TList enumerator that only knows items as pointers.
To use the
Code: Pascal  [Select]
  1. for obj in anObjectList do

syntax you have to provide your own enumerator which can handle the desired type. Otherwise, as you found, FPC complains about the incompatible type of obj with what the enumerator expects.
See the attached project which tidies up the code you provided, removes the memory leaks, and adds a suitable enumerator which knows the TMyObject type.

PatBayford

  • Jr. Member
  • **
  • Posts: 60
Re: TCollection question
« Reply #9 on: June 20, 2017, 11:58:50 pm »
TObjectList's default enumerator is the TList enumerator that only knows items as pointers.
To use the
Code: Pascal  [Select]
  1. for obj in anObjectList do

syntax you have to provide your own enumerator which can handle the desired type. Otherwise, as you found, FPC complains about the incompatible type of obj with what the enumerator expects.
See the attached project which tidies up the code you provided, removes the memory leaks, and adds a suitable enumerator which knows the TMyObject type.
Thanks for that Howard - the enumerator problem was not what I intended to introduce when I simplified the code - did NOT check the ancestor tree carefully enough! I thought TObjectList would at least handle objects. Will check through your revised code and take amendments/suggestions on board - I am trying hard to learn the differences between Delphi and  Lazarus. The provision of the Enumerator by the latter makes life a whole lot easier when dealing with largish lists, not to mention removing a whole heap of run-time errors.
Lazarus 1.6.4 FPC 3.0.2 SVN 54278 Windows 10 64bit

PatBayford

  • Jr. Member
  • **
  • Posts: 60
Re: TCollection question
« Reply #10 on: June 22, 2017, 01:27:29 am »
Have reviewed your code - this is the way I would normally handle the problem (i.e. typecasting the Get functions), however, I was originally looking for a less coding intensive solution - some of my code has a whole load of sub-classes, and just finding all the Get/Set methods is time consuming.
I guess Pascal's strong typing and OOP do not really make comfortable bedfellows!
What would be really neat would be an implementation that worked rather like Smalltalk, where you only ever re-code these methods to achieve totally different behaviours for the sub-classes, everything else is handled by the polymorphism.
Lazarus 1.6.4 FPC 3.0.2 SVN 54278 Windows 10 64bit

howardpc

  • Hero Member
  • *****
  • Posts: 2251
Re: TCollection question
« Reply #11 on: June 22, 2017, 09:57:56 am »
It is a classic dilemma: the tradeoff between type safety (usually more code has to be written) and simplicity of coding (the one-size-fits-all variants approach seen a lot in early Basic).

marcov

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 5575
Re: TCollection question
« Reply #12 on: June 22, 2017, 01:36:54 pm »
Or the joy of writing code in an very expressive language, and the horror of having to understand it later.

michalis

  • Jr. Member
  • **
  • Posts: 87
    • Homepage
Re: TCollection question
« Reply #13 on: June 22, 2017, 02:19:58 pm »
Have reviewed your code - this is the way I would normally handle the problem (i.e. typecasting the Get functions), however, I was originally looking for a less coding intensive solution - some of my code has a whole load of sub-classes, and just finding all the Get/Set methods is time consuming.
I guess Pascal's strong typing and OOP do not really make comfortable bedfellows!
What would be really neat would be an implementation that worked rather like Smalltalk, where you only ever re-code these methods to achieve totally different behaviours for the sub-classes, everything else is handled by the polymorphism.

IMHO, generics really solve this case well. Try to use FGL unit I mentioned earlier (it's in all FPC versions at least >= 2.6.2), e.g. specialize TFPGObjectList<TMyType> . This gives you a working list of TMyType, without ever needing to do "Items[ I ] as TMyType". That's why I like generics so much, I often have a lot of containers in my code :)

Thaddy

  • Hero Member
  • *****
  • Posts: 4278
Re: TCollection question
« Reply #14 on: June 22, 2017, 02:46:39 pm »
This gives you a working list of TMyType, without ever needing to do "Items[ I ] as TMyType".
That's not what OP means. He means that since classes are derived from a base type, several classes can immediately be recognized as the correct type. T1 = class; T2 = class(t1); T3= class(t1) and all within the same collection.
Based on the available information one can determine the real class (T2 or T3) based on inference. That's because classtype is known at declare time. But this mechanism is not implemented. It can be implemented, but type inference is not something people like here. Something like this is already possible with generics and assignment (to the proper type), though. I just wrote an example in another thread that basically can do that.
http://forum.lazarus.freepascal.org/index.php/topic,37278.msg250152.html#msg250152

If it is not immediately obvious to you, then I will explain the relation between the subjects.
Look at the way I handle the strongly typed  versions of TPersistent decendents...
It is perfectly feasible to do that for a generic collection, but only from the rtl-generics...
« Last Edit: June 22, 2017, 02:57:01 pm by Thaddy »
"Logically, no number of positive outcomes at the level of experimental testing can confirm a scientific theory, but a single counterexample is logically decisive."

 

Recent

Get Lazarus at SourceForge.net. Fast, secure and Free Open Source software downloads Open Hub project report for Lazarus