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?
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With