Recent

Author Topic: OOP and generics: TFPGMap and custom lists (or dictionaries) with "as" operator.  (Read 4385 times)

kr1d

  • New Member
  • *
  • Posts: 11
Is it fine to do something like this or there is a better solution?

I dont like "as motoObject" part (line 38).

I dont want to make specialized lists for every type(TMotoObject, TCarObject) because I will need to repeat my code there.

so overall I want to make a list that supports TAnyObject descendants.

Code: Pascal  [Select][+][-]
  1.     TAnyObject=class
  2.         ...
  3.     end;
  4.     ​
  5.     TAOList=class (specialize TFPGMap<string,TAnyObject>)
  6.         ...
  7.         procedure addAO(name:string;AO:TAnyObject);
  8.         function getAO(name:string):TAnyObject;
  9.     end;
  10.     ​
  11.     TCarObject=class (TAnyObject)
  12.         hp:word;
  13.         ...
  14.     end;
  15.     ​
  16.     TMotoObject=class (TAnyObject)
  17.         hp:word;
  18.         cc:word;
  19.         ...
  20.     end;
  21.     ​
  22.     TCarList=class(TAOList)
  23.         ...
  24.     end;
  25.     ​
  26.     TMotoList=class(TAOList)
  27.         ...
  28.     end;
  29.     ​
  30.     var
  31.         carlist:TCarList;
  32.         motolist:TMotoList;
  33.     ...
  34.     ​
  35.     carlist.addAO('subawu impreza',TCarObject.Create);
  36.     motolist.addAO('yamaha r1',TMotoObject.Create);
  37.     ​
  38.     moto:=motolist.getAO('yamaha r1') as TMotoObject; // ???
  39.     ​
  40.     moto.hp:=1000;
  41.  
« Last Edit: October 17, 2021, 02:01:19 pm by kr1d »

Thaddy

  • Hero Member
  • *****
  • Posts: 14169
  • Probably until I exterminate Putin.
If TAnyObject would derive from TInterfaced object and the interface contract is obeyed by all descendants that is indeed possible.
Specialize a type, not a var.

Warfley

  • Hero Member
  • *****
  • Posts: 1499
Yes, AS is the correct way of doing this. It even encorporates error checking, meaning if the object is not of the type you trying to cast it to an exception will be raised.

If TAnyObject would derive from TInterfaced object and the interface contract is obeyed by all descendants that is indeed possible.
As is not only for interfaces, but works fine on any class type.

Example:
Code: Pascal  [Select][+][-]
  1. program Project1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. type
  6.   TBase = class
  7.   end;
  8.  
  9.   TFoo = class(TBase)
  10.   public procedure Foo;
  11.   end;
  12.  
  13.   TBar = class(TBase)
  14.   public procedure Bar;
  15.   end;
  16.  
  17. procedure TFoo.Foo;
  18. begin
  19.   WriteLn('Foo');
  20. end;
  21.  
  22. procedure TBar.Bar;
  23. begin
  24.   WriteLn('Bar');
  25. end;
  26.  
  27. var
  28.   base: TBase;
  29. begin
  30.   base := TFoo.Create;
  31.   (base as TFoo).Foo;
  32.   (base as TBar).Bar; // Exception because it is not a TBar
  33. end.  

Thaddy

  • Hero Member
  • *****
  • Posts: 14169
  • Probably until I exterminate Putin.
As is not only for interfaces, but works fine on any class type.
NO, that example relies on a base class that is the same. The counter example (and so Popper's falcification : to mean the statement can not be true if given an example that fails the paradigm (assumption)) is with UNrelated classes that implement THE SAME interface contract. In that way generics will work without needing to check anything else than the interface. And since Interface is a generics constraint, that happens at compile time.

So any class "type"? My "Donkey". Of course not!
« Last Edit: October 17, 2021, 04:24:27 pm by Thaddy »
Specialize a type, not a var.

Warfley

  • Hero Member
  • *****
  • Posts: 1499
What kind of interface are you talking about? The generic class TFPGMap works with any class type, no interface needed.

Code: Pascal  [Select][+][-]
  1. program Project1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. uses
  6.   fgl;
  7.  
  8. type
  9.   TMyMap = specialize TFPGMap<String, TObject>;
  10.  
  11.   TFoo = class
  12.   public procedure Foo;
  13.   end;
  14.  
  15.   TBar = class
  16.   public procedure Bar;
  17.   end;
  18.  
  19. procedure TFoo.Foo;
  20. begin
  21.   WriteLn('Foo');
  22. end;
  23.  
  24. procedure TBar.Bar;
  25. begin
  26.   WriteLn('Bar');
  27. end;
  28.  
  29. var
  30.   map: TMyMap;
  31. begin
  32.   map := TMyMap.Create;
  33.   map.Add('Foo', TFoo.Create);
  34.   (map['Foo'] as TFoo).Foo;
  35.   (map['Foo'] as TBar).Bar; // Throws exception because it is of type TFoo not TBar
  36. end.  
  37.  
Neither TFoo nor TBar implement any common interface. The only thing that they share is that they both inherit from TObject, but thats true for every class.

You can even go further:
Code: Pascal  [Select][+][-]
  1.   TMyMap = specialize TFPGMap<String, Pointer>;
  2. ...
  3.   (TObject(map['Foo']) as TFoo).Foo;
  4.   (TObject(map['Foo']) as TBar).Bar;
« Last Edit: October 17, 2021, 06:50:38 pm by Warfley »

kr1d

  • New Member
  • *
  • Posts: 11
Yes, AS is the correct way of doing this. It even encorporates error checking, meaning if the object is not of the type you trying to cast it to an exception will be raised.

@Warfley But I kinda dont like "as" operator here.
In perfect world I'd like to write it without "as" operator.
Simply:
Code: Pascal  [Select][+][-]
  1. moto:=motolist.getAO('yamaha r1');

@Thaddy Not sure about interface thing because I didnt even tried interfaces. Any book suggestions or good article about that?

PascalDragon

  • Hero Member
  • *****
  • Posts: 5444
  • Compiler Developer
@Warfley But I kinda dont like "as" operator here.
In perfect world I'd like to write it without "as" operator.
Simply:
Code: Pascal  [Select][+][-]
  1. moto:=motolist.getAO('yamaha r1');

The way you declared your classes that is the only way. But if you play around a bit more with generics you can do better:

Code: Pascal  [Select][+][-]
  1. program tgenlist;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. uses
  6.   fgl;
  7.  
  8. type
  9.   TAnyObject = class
  10.  
  11.   end;
  12.  
  13.   generic TGenAOList<T: TAnyObject> = class(specialize TFPGMap<String, T>)
  14.  
  15.   end;
  16.  
  17.   { type for any TAnyObject descendant }
  18.   TAOList = specialize TGenAOList<TAnyObject>;
  19.  
  20.   TCarObject = class(TAnyObject)
  21.  
  22.   end;
  23.  
  24.   TMotoObject = class(TAnyObject)
  25.  
  26.   end;
  27.  
  28.   TCarList = specialize TGenAOList<TCarObject>;
  29.  
  30.   TMotoList = specialize TGenAOList<TMotoObject>;
  31.  
  32. var
  33.   cl: TCarList;
  34.   ml: TMotoList;
  35.   moto: TMotoObject;
  36. begin
  37.   cl := TCarList.Create;
  38.   try
  39.     ml := TMotoList.Create;
  40.  
  41.     cl.Add('subawu impreza', TCarObject.Create);
  42.     ml.Add('yamaha r1', TMotoObject.Create);
  43.  
  44.     { no "as" here }
  45.     moto := ml['yamaha r1'];
  46.  
  47.   finally
  48.     cl.Free;
  49.     ml.Free;
  50.   end;
  51. end.

Important caveat: you can not pass a TMotoList to something that takes a TAOList (at least not without casting and based on the assumption that they have the same memory layout which in this situation is true and shouldn't lead to problems).

Additional sidenote: you should use a TFPGMapObject instead of TFPGMap, because the former will free instances when they are deleted from the map (or the map itself is freed) otherwise you'd have to manually free them or have memory leaks.

kr1d

  • New Member
  • *
  • Posts: 11
@PascalDragon
This is exactly what I was looking for! Big thank you!

 

TinyPortal © 2005-2018