2016-06-10 5 views
7

Я использую Delphi 2007 для поддержки старого проекта, у меня есть проблема с доступом к константам класса из переменной Reference класса, я получаю всегда постоянную родительского класса вместо дочерних ,Доступ к константам класса из ссылочной переменной класса в Delphi

Предположим, что родительский класс, некоторые дочерние классы, ссылка на класс и, наконец, массив 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 

Я ожидал увидеть «РЕБЕНКА 1» и «РЕБЕНОК 2» также в цикле массива!

Может ли кто-нибудь объяснить мне, почему это не работает с ссылкой на класс?

+1

Вам нужен виртуальный метод для реализации полиморфизма , . – kludg

ответ

7

Неизученная константа класса - это нормальная константа с добавлением некоторой области видимости.
Типичная константа класса - это действительно переменная класса, которую вы не можете изменить.
Проблема в том, что переменные класса не являются виртуальными.

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. 
+0

спасибо Johan, подклассовое решение функции класса работает как шарм. Очень интересно также сообщение Hallvard Vassbotn. Я не могу представить, что это была такая старая проблема .. и до сих пор не решена! : D – MtwStark

+1

@MtwStark, теперь, когда у нас есть атрибуты, проблема в значительной степени исчезла. – Johan

Смежные вопросы