[Lazarus] Generics type as parameter

Leonardo M. Ramé l.rame at griensu.com
Sat May 12 18:07:18 CEST 2012


On 2012-05-12 17:00:22 +0200, Sven Barth wrote:
> On 12.05.2012 13:32, Leonardo M. Ramé wrote:
> >I'm trying to work in an abstract way with generics, I need to create a
> >method receiving a un-specialized generic and do some work on it.
> >
> >Example:
> >
> >function ProcessGeneric(AList: TFPGList);
> >begin
> >   ... do something ...
> >end;
> >
> >Imagine your application uses many lists of specialized objects, such as
> >TCustomer, TBooks, TColors. To create a list of those types, you'd do
> >something like this:
> >
> >   TCustomers = specialize TFPGList<TCustomer>;
> >   TBooks = specialize TFPGList<TBook>;
> >   TColors = specialize TFPGList<TColor>;
> >
> >Then, you need to pass any of those types to "ProcessGeneric":
> >
> >var
> >   lCustomers: TCustomers;
> >   lBooks: TBooks;
> >   lColors: TColors;
> >
> >begin
> >   ProcessGeneric(lCustomers);
> >   ProcessGeneric(lBooks);
> >   ProcessGeneric(lColors);
> >end;
> >
> >Without generics, I can use, for example, a TObjectList, or a
> >TCollection, without any problem. Is there a way to do this using
> >Generics?.
> 
> There are basically to ways you can achive this (one is supported by FPC the
> other not yet):
> * descend the generic from a baseclass you can work with (e.g. as Michalis
> already mentioned the FGL classes all descend from a baseclass that is
> tailored to the needs of the generic, e.g. TFPGList descends from TFPSList)
> * use generic procedures (this is not yet supported with FPC, it's on my
> ToDo list though).
> 
> In case of generic procedures your example will then look like this (in mode
> Delphi):
> 
> === example begin ===
> 
> procedure ProcessGeneric<T>(AList: TFPGList<T>);
> begin
>   ...
> end;
> 
> var
>    lCustomers: TCustomers;
>    lBooks: TBooks;
>    lColors: TColors;
> 
> begin
>    ProcessGeneric<TCustomer>(lCustomers);
>    ProcessGeneric<TBook>(lBooks);
>    ProcessGeneric<TColor>(lColors);
> end;
> 
> === example end ===
> 
> (in mode ObjFPC there would be a bit more "generic" and "specialize" of
> course)
> 
> In mode Delphi you can also use a workaround in the current version (2.7.1)
> of FPC (it should work at least, but I haven't tested it...):
> 
> === workaround begin ===
> 
> TMyListProcessor<T> = class
>   class procedure ProcessGeneric(AList: TFPGList<T>);
> end;
> 
> class procedure TMyListProcessor<T>(AList: TFPGList<T>);
> begin
> 
> end;
> 
> var
>    lCustomers: TCustomers;
>    lBooks: TBooks;
>    lColors: TColors;
> 
> begin
>    TMyListProcessor<TCustomer>.ProcessGeneric(lCustomers);
>    TMyListProcessor<TBook>.ProcessGeneric(lBooks);
>    TMyListProcessor<TColor>.ProcessGeneric(lColors);
> end;
> 
> === workaround end ===
> 
> This won't work as written above in mode ObjFPC (yet), because you'd always
> need to specialize the class in a type section.
> 
> Regards,
> Sven
> 

Thanks Sven and Michalis. Using the suggestion by Michalis, I got a
solution mixing Generics and regular objects.

What I'm pursuing is to replace my TCollection based ORM, with a
Generics based one, this allow users to write less code, in a clearer
way.

The way the ORM works, is to create a TCollection instance, then execute
an ORM's method to load/save data into/from it, example:

var
  lCustomers: TCustomers; // this is a TCollection descendant
begin
  lCustomers := TCustomers.Create;
  FConnector.LoadData(lCustomers, []);
  ... do something with lCustomers ...
  lCustomers.Free;
end;

As you can see, FConnector.LoadData receives a TCollection as parameter,
and using RTTI, it fills each TCustomer published property.

With Michalis's solution, I can turn my ORM from TCollections to
Generics with very little changes, see how small is the unit customer
now, instead of a complete TCollection/TCollectionItem definition:

///// ---- customer.pas ----
unit customer;
{$mode objfpc}

interface

uses
  Classes;

type
  TCustomer = class
  private
    FName: string;
    FAge: Integer;
  published
    property Name: string read FName write FName;
    property Age: Integer read FAge write FAge;
  end;

implementation

end.


///// ---- generics.pas ----
program generics;

{$mode objfpc}

uses
  sysutils,
  customer,
  typinfo,
  variants,
  fgl;

type
  TCustomerList = specialize TFPGList<TCustomer>;

procedure ProcessGeneric(AList: TFPSList);
var
  I: Integer;
  lVal: string;
begin
  for I:= 0 to AList.Count - 1 do
  begin
    lVal := string(GetPropValue(TObject(AList.Items[I]^), 'Name')); 
    writeln(Format('%s (%d)', [lVal, I]));
  end;
end;

var
  lList: TCustomerList;
  lCustomer: TCustomer;

begin
  lList := TCustomerList.Create;
  lCustomer := TCustomer.Create;
  lCustomer.Name := 'Martín';
  lCustomer.Age := 37;
  lList.Add(lCustomer);

  lCustomer := TCustomer.Create;
  lCustomer.Name := 'Nico';
  lCustomer.Age := 28;
  lList.Add(lCustomer);

  ProcessGeneric(lList);  

  lList.Free;
end.

-- 
Leonardo M. Ramé
http://leonardorame.blogspot.com




More information about the Lazarus mailing list