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;