Generics & Collections
Pascal generics are not templates with duck typing: inside a generic, you can only use operations the constraints guarantee.
29.1 Two syntaxes
Delphi / FPC {$mode delphi} FPC {$mode objfpc} Declare generic type TStack<T> = class... end; generic TStack<T> = class... end; Use (specialize) specialize TStack<Integer> Generic function (FPC 3.2+) function Max<T>(A, B: T): T;
generic function Max<T>(A, B: T): T; called as specialize Max<Integer>(1, 2) Implementing a method function TStack<T>.Pop: T; function TStack.Pop: T;
29.2 A generic stack (Delphi syntax, compiles with Delphi and FPC {$mode delphi})
program GenericStack;
{$IFDEF FPC}{$mode delphi}{$ENDIF}
{$APPTYPE CONSOLE}
uses SysUtils;
type
TStack<T> = class
private
FItems: array of T;
FCount: Integer;
public
procedure Push(const Item: T);
function Pop: T;
function Peek: T;
function IsEmpty: Boolean;
property Count: Integer read FCount;
end;
procedure TStack<T>.Push(const Item: T);
begin
if FCount = Length(FItems) then
SetLength(FItems, FCount * 2 + 4); // grow geometrically
FItems[FCount] := Item;
Inc(FCount);
end;
function TStack<T>.Pop: T;
begin
if FCount = 0 then
raise Exception.Create('Stack is empty');
Dec(FCount);
Result := FItems[FCount];
FItems[FCount] := Default(T); // release managed values
end;
function TStack<T>.Peek: T;
begin
if FCount = 0 then
raise Exception.Create('Stack is empty');
Result := FItems[FCount - 1];
end;
function TStack<T>.IsEmpty: Boolean;
begin
Result := FCount = 0;
end;
var
Nums: TStack<Integer>;
Words: TStack<string>;
begin
Nums := TStack<Integer>.Create;
Words := TStack<string>.Create;
try
Nums.Push(1); Nums.Push(2); Nums.Push(3);
Words.Push('generic'); Words.Push('Pascal');
while not
…29.3 The same in objfpc syntax
{$mode objfpc}{$H+}
type
generic TPair<TKey, TValue> = record
Key: TKey;
Value: TValue;
end;
TNamedInt = specialize TPair<string, Integer>;
generic function Max<T>(const A, B: T): T;
begin
if A > B then Result := A else Result := B;
end;
var
P: TNamedInt;
begin
P.Key := 'answer';
P.Value := 42;
Writeln(P.Key, '=', P.Value);
Writeln(specialize Max<Integer>(3, 7)); // 7
Writeln(specialize Max<string>('a', 'b')); // b
end.29.4 Constraints
any class type any non-nullable value type has a public parameterless Create descends from TBaseClass IInterface-derived type implements that interface T: TComponent, IMyIntf
Pascal generics are not templates with duck typing: inside a generic, you can only use operations the constraints guarantee. Comparisons like A > B on an unconstrained T compile in FPC (checked at specialization) but not in Delphi — use TComparer<T>.Default.Compare(A, B) from Generics.Defaults for portable code.
type
TRepository<T: class, constructor> = class // T must be a class with a parameterless constructor
function NewItem: T;
end;
TValueBox<T: record> = record // T must be a value type
Value: T;
end;
TSortedList<T: IComparable> = class // T must implement an interface
end;
function TRepository<T>.NewItem: T;
begin
Result := T.Create;
end;29.5 Generics.Collections
Available in Delphi (System.Generics.Collections) and in FPC 3.2+ (Generics.Collections):
dynamic list: Add, Insert, Delete, IndexOf, Sort, BinarySearch, Count, Items[] list that frees its objects (OwnsObjects:= True by default) TDictionary<TKey, TValue> hash map: Add, AddOrSetValue, TryGetValue, ContainsKey, Remove, Keys, Values TObjectDictionary<K, V> dictionary owning keys and/or values ([doOwnsValues]) TStack<T>, TQueue<T> TObjectStack<T>, TObjectQueue<T> set of unique values (recent Delphi versions, FPC Generics.Collections) class methods Sort<T>, BinarySearch<T>, Copy<T>
Custom sort order with an anonymous comparer (Delphi; FPC trunk with anonymous functions):
program Collections;
{$IFDEF FPC}{$mode delphi}{$ENDIF}
{$APPTYPE CONSOLE}
uses SysUtils, Generics.Collections; // Delphi: System.Generics.Collections (or unit scope names)
var
Ages: TDictionary<string, Integer>;
Names: TList<string>;
Pair: TPair<string, Integer>;
Age: Integer;
N: string;
begin
Ages := TDictionary<string, Integer>.Create;
Names := TList<string>.Create;
try
Ages.Add('Alice', 31);
Ages.Add('Bob', 25);
Ages.AddOrSetValue('Alice', 32); // update
if Ages.TryGetValue('Bob', Age) then
Writeln('Bob is ', Age);
for Pair in Ages do // order is not defined
Writeln(Pair.Key, ': ', Pair.Value);
Names.AddRange(['Charlie', 'alice', 'Bob']);
Names.Sort; // default comparer (case-sensitive ordinal)
for N in Names do Write(N, ' ');
Writeln;
finally
Names.Free;
Ages.Free;
end;
end.uses System.Generics.Defaults;
...
People.Sort(TComparer<TPerson>.Construct(
function(const L, R: TPerson): Integer
begin
Result := CompareText(L.Name, R.Name);
if Result = 0 then
Result := L.Age - R.Age;
end));29.6 FGL (Free Generics Library)
FPC's older, lightweight generic containers in unit fgl:
TFPGList<T> requires T to support =; TFPGObjectList<T> owns objects; TFPGMap<K, V> is a sorted map. Prefer Generics.Collections for new code; FGL is still common in Lazarus projects.
{$mode objfpc}{$H+}
uses fgl;
type
TIntList = specialize TFPGList<Integer>;
TWordCount = specialize TFPGMap<string, Integer>;
var
L: TIntList;
M: TWordCount;
I: Integer;
begin
L := TIntList.Create;
M := TWordCount.Create;
try
L.Add(5); L.Add(2); L.Add(9);
for I in L do Write(I, ' ');
Writeln;
M.Sorted := True;
M['pascal'] := 3;
M['delphi'] := 1;
Writeln(M['pascal'], ' ', M.IndexOf('delphi'));
finally
M.Free;
L.Free;
end;
end.