Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Accessing Class Constants from a Class Reference variable in Delphi

I'm using Delphi 2007 to maintain an old project, I have a problem accessing class constants from a Class Reference variable, I get always the parent class constant instead of the children one.

Suppose to have a parent class, some child classes, a class reference and finally a const array to store the class references for looping purposes.

take a look at following simple program:

program TestClassConst;

{$APPTYPE CONSOLE}

uses
  SysUtils;

type

  TParent = class
  const
    ClassConst = 'BASE CLASS';
  end;

  TChild1 = class(TParent)
  const
    ClassConst = 'CHILD 1';
  end;

  TChild2 = class(TParent)
  const
    ClassConst = 'CHILD 2';
  end;

  TParentClass = class of TParent;
  TChildClasses = array[0..1] of TParentClass;

const
  ChildClasses: TChildClasses = (TChild1, TChild2);

var
  i: integer;
  c: TParentClass;
  s: string;

begin
  try
    writeln;

    writeln('looping through class reference array');
    for i := low(ChildClasses) to high(ChildClasses) do begin
      c := ChildClasses[i];
      writeln(c.ClassName, ' -> ', c.ClassConst);
    end;

    writeln;

    writeln('accessing classes directly');
    writeln(TChild1.ClassName, ' -> ', TChild1.ClassConst);
    writeln(TChild2.ClassName, ' -> ', TChild2.ClassConst);

  except
    on E: Exception do
      Writeln(E.Classname, ': ', E.Message);
  end;
end.

When it runs I get:

looping through class reference array
TChild1 -> BASE CLASS
TChild2 -> BASE CLASS

accessing classes directly
TChild1 -> CHILD 1
TChild2 -> CHILD 2

I expected to see 'CHILD 1' and 'CHILD 2' also in array loop!

Can anyone explain me why it does not work with class reference?

like image 401
MtwStark Avatar asked Jun 10 '16 13:06

MtwStark


1 Answers

An untyped class constant is a normal constant with some scoping added.
A typed class constant is really a class variable that you cannot change.
The problem is that the class variables are not virtual.

Hallvard Vassbotn has written about this issue here: Part 1, Part 2

You cannot access class variables and class constants from a class reference because the language does not have support for virtual class variables.
When you say s:= TClass1.SomeConst the compiler translates this into s:= SomeGlobalButHiddenConst before moving on with the rest of the compilation.

class var and class const are nothing more than syntactic sugar.
As such the link between the class var/const and the actual class only exists during compile-time, it is broken come run-time, much like type-erasure in Java.

RTTI also does not help: Get constant fields from a class using RTTI
I guess if you're using D2007 your only option is to declare a virtual function that returns the constant you want:

Pre D2010 option: virtual method

TParent = class
  class function Name: string; virtual;
end;

TChild1 = class(TParent)
  class function name: string; override;
....
class function TParent.name: string;
begin
  Result:= Self.ClassConst;
end;

class function TChild1.name: string;
begin
  Result:= Self.ClassConst;   //Silly copy paste solution
end;

This is a sad state of affairs, but I don't see another option.

From Delphi 2010 onwards: use attributes
A better option is to use attributes, these you can access using RTTI:

The following code works:

program TestClassConst;

{$APPTYPE CONSOLE}

uses
  SysUtils, rtti;

type

  NameAttribute = class(TCustomAttribute)
  private
    Fname: string;
  public
    constructor Create(const Name: string);
    property Name: string read Fname;
  end;

  [Name('Base class')]
  TParent = class
  const
    ClassConst = 'BASE CLASS';
  private
  public
    class function Name: string;
  end;

  [Name('Child 1')]
  TChild1 = class(TParent)
  const
    ClassConst = 'CHILD 1';
  end;

  [Name('Child 2')]
  TChild2 = class(TParent)
  const
    ClassConst = 'CHILD 2';
  end;

  TParentClass = class of TParent;
  TChildClasses = array[0..1] of TParentClass;

const
  ChildClasses: TChildClasses = (TChild1, TChild2);

var
  i: integer;
  c: TParentClass;
  s: string;

{ TParent }

class function TParent.Name: string;
var
  Context: TRttiContext;
  ClassData: TRttiType;
  Attr: TCustomAttribute;
begin
  Context:= TRttiContext.Create;
  ClassData:= Context.GetType(Self);
  try
    for Attr in ClassData.GetAttributes do begin
      if Attr is NameAttribute then Result:= NameAttribute(Attr).Name;
    end;
  finally
    ClassData.Free;
  end;
end;

{ NameAttribute }

constructor NameAttribute.Create(const Name: string);
begin
  inherited Create;
  FName:= name;
end;

begin
  writeln;

  writeln('looping through class reference array');
  for i := low(ChildClasses) to high(ChildClasses) do begin
    c := ChildClasses[i];
    writeln(c.ClassName, ' -> ', c.Name);
  end;

  writeln;

  writeln('accessing classes directly');
  writeln(TChild1.ClassName, ' -> ', TChild1.Name);
  writeln(TChild2.ClassName, ' -> ', TChild2.Name);
  readln;
end.
like image 195
Johan Avatar answered Nov 14 '22 16:11

Johan