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.