Class Members, Metaclasses & Type Checks

A metaclass type holds a class rather than an instance: TShapeClass = class of TShape;.

27.1 Class methods, class variables, and class properties

Class methods receive the class reference as Self and can be virtual.

Static class methods (static) have no Self; they are required for class property accessors in Delphi.

Class constructors/destructors (class constructor Create; class destructor Destroy;) run once, like unit initialization/finalization, to set up class variables (Delphi 2010+, FPC 3.0+).

type
  TCounter = class
  strict private
    class var FInstances: Integer;           // one copy shared by all instances
  public
    constructor Create;
    destructor Destroy; override;
    class function Instances: Integer;       // can be called on the class
    class function Describe: string; virtual;
    class procedure Reset; static;           // no Self at all (like a plain function)
    class property Count: Integer read FInstances;
  end;

constructor TCounter.Create;
begin
  inherited Create;
  Inc(FInstances);
end;

destructor TCounter.Destroy;
begin
  Dec(FInstances);
  inherited Destroy;
end;

class function TCounter.Instances: Integer;
begin
  Result := FInstances;
end;

class function TCounter.Describe: string;
begin
  Result := ClassName + ' class';           // Self is the class (metaclass) here
end;

class procedure TCounter.Reset;
begin
  FInstances := 0;
end;

// usage: Writeln(TCounter.Instances, ' ', TCounter.Count, ' ', TCounter.Describe);

27.2 Nested types and constants

type
  TParser = class
  public
    type
      TTokenKind = (tkNumber, tkIdent, tkOperator, tkEOF);
    const
      MaxDepth = 64;
  end;

var
  K: TParser.TTokenKind;

27.3 Metaclasses (class references)

A metaclass type holds a class rather than an instance: TShapeClass = class of TShape;. Combined with virtual constructors, it enables factories:

This is exactly how the LCL/VCL streaming system recreates components from .lfm/.dfm files (RegisterClass, FindClass, TComponentClass).

type
  TPlugin = class
  public
    constructor Create; virtual;
    function Name: string; virtual; abstract;
  end;
  TPluginClass = class of TPlugin;

  TSpellChecker = class(TPlugin)
    function Name: string; override;
  end;
  TExporter = class(TPlugin)
    function Name: string; override;
  end;

constructor TPlugin.Create;
begin
  inherited Create;
end;

function TSpellChecker.Name: string; begin Result := 'Spell checker'; end;
function TExporter.Name: string;     begin Result := 'Exporter'; end;

function MakePlugin(AClass: TPluginClass): TPlugin;
begin
  Result := AClass.Create;     // virtual constructor: creates the right descendant
end;

var
  Registry: array of TPluginClass;
  C: TPluginClass;
  P: TPlugin;
begin
  Registry := [TSpellChecker, TExporter];
  for C in Registry do
  begin
    P := MakePlugin(C);
    try
      Writeln(C.ClassName, ' -> ', P.Name);
    finally
      P.Free;
    end;
  end;
end.

27.4 Type checks: is and as

Obj is TClass True if Obj is non-nil and an instance of TClass or a descendant Obj as TClass checked cast; nil passes through; raises EInvalidCast on mismatch unchecked hard cast — fastest, unsafe if wrong Obj.ClassType = TClass exact class match (no descendants) same as is for a non-nil object

Intf as IOther, Supports(Obj, IOther, X) interface queries (Chapter 28)

Delphi 13 adds is not (e.g. if Obj is not TCircle then); for portable code write not (Obj is TCircle).

procedure Process(Obj: TObject);
begin
  if Obj is TCircle then                         // type test (true for descendants too)
    Writeln('Circle area: ', TCircle(Obj).Area:0:2);   // unchecked hard cast after the test

  (Obj as TShape).Describe;                      // checked cast: raises EInvalidCast if wrong
end;