我正在使用Delphi 2007维护一个旧项目,我在从Class Reference变量访问类常量时遇到问题,我总是得到父类常量而不是子类常量。
假设有一个父类,一些子类,一个类引用,最后是一个const数组来存储类引用以进行循环。
看看以下简单程序:
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.
当它运行时,我得到:
looping through class reference array
TChild1 -> BASE CLASS
TChild2 -> BASE CLASS
accessing classes directly
TChild1 -> CHILD 1
TChild2 -> CHILD 2
我希望在阵列循环中看到'CHILD 1'和'CHILD 2'!
任何人都可以解释为什么它不能用于类引用吗?
答案 0 :(得分:8)
无类型类常量是一个常规常量,并添加了一些范围。
类型化常量实际上是一个你无法改变的类变量
问题是类变量不是虚拟的。
Hallvard Vassbotn在此处撰写了有关此问题的文章:Part 1,Part 2
您无法从类引用访问类变量和类常量,因为该语言不支持虚拟类变量。
当你说s:= TClass1.SomeConst
时,编译器会将其转换为s:= SomeGlobalButHiddenConst
,然后再继续编译。
class var
和class const
只不过是语法糖
因此,class var/const
和实际类之间的链接仅在编译时存在,它在运行时被破坏,就像Java中的类型擦除一样。
RTTI也无济于事:Get constant fields from a class using RTTI
我想如果你正在使用D2007,你唯一的选择是声明一个返回你想要的常量的虚函数:
Pre D2010选项:虚拟方法
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;
这是一种令人悲伤的事态,但我没有看到另一种选择。
From Delphi 2010 onwards:使用attributes
更好的选择是使用属性,您可以使用RTTI:
以下代码有效:
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.