Generics

22.1 Generic Types

type
  TStack<T> = class
  private
    FItems: TList<T>;
  public
    constructor Create;
    destructor Destroy; override;
    procedure Push(const Item: T);
    function Pop: T;
    function Peek: T;
    function IsEmpty: Boolean;
  end;

constructor TStack<T>.Create;
begin
  inherited;
  FItems := TList<T>.Create;
end;

destructor TStack<T>.Destroy;
begin
  FItems.Free;
  inherited;
end;

procedure TStack<T>.Push(const Item: T);
begin
  FItems.Add(Item);
end;

function TStack<T>.Pop: T;
begin
  if FItems.Count = 0 then raise EInvalidOpException.Create('Stack empty');
  Result := FItems.Last;
  FItems.Delete(FItems.Count - 1);
end;

function TStack<T>.Peek: T; begin Result := FItems.Last; end;
function TStack<T>.IsEmpty: Boolean; begin Result := FItems.Count = 0; end;

var S := TStack<Integer>.Create;
try
  S.Push(1); S.Push(2);
  Writeln(S.Pop);
finally
  S.Free;
end;

type
  TPair<TKey, TValue> = record
    Key: TKey;
    Value: TValue;
    constructor Create(const AKey: TKey; const AValue: TValue);
  end;

22.2 Generic Methods

Delphi has no free-standing generic procedures. Generic methods live on classes/records (often class/static methods).

type
  TUtils = class
    class function Max<T>(const A, B: T): T; static;
    class procedure Swap<T>(var A, B: T); static;
  end;

class function TUtils.Max<T>(const A, B: T): T;
begin
  if TComparer<T>.Default.Compare(A, B) > 0 then Result := A else Result := B;
end;

class procedure TUtils.Swap<T>(var A, B: T);
begin
  var Tmp := A; A := B; B := Tmp;
end;

Writeln(TUtils.Max<Integer>(3, 7), ' ', TUtils.Max<string>('a', 'b'));

22.3 Constraints

Must be a class type Must be a value type (non-nullable) T: constructor

Has a parameterless Create (so T.Create is allowed) T: TBaseClass Descends from the class T: IInterface Implements the interface T: class, constructor

type
  TRepository<T: TEntity, constructor> = class
    function CreateNew: T;
  end;
function TRepository<T>.CreateNew: T;
begin
  Result := T.Create;
end;

22.4 Generics Notes

Compile-time type checking. Code is generated per instantiation (which can grow executable size).

Use Default(T) for default values, TypeInfo(T) / GetTypeKind(T) (compile-time intrinsic) for type-specific branches, and IsManagedType(T).

There's no variance, and operators on T aren't available. Use TComparer<T>, TEqualityComparer<T>, or supplied function parameters.