Sven Barth wrote 103 lines
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.