2016-06-10 17 views
7

Sto usando Delphi 2007 per mantenere un vecchio progetto, ho un problema nell'accedere alle costanti di classe da una variabile di riferimento di classe, ottengo sempre la costante della classe genitrice invece di quella dei figli .Accesso alle costanti di classe da una variabile di riferimento di classe in Delphi

Supponiamo di avere una classe genitore, alcune classi figlie, un riferimento di classe e infine un array const per memorizzare i riferimenti di classe per scopi di ciclo.

un'occhiata al seguente programma semplice:

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. 

Quando si corre ottengo:

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

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

mi aspettavo di vedere 'BAMBINO 1' e 'bambino 2' anche in loop serie!

Qualcuno può spiegarmi perché non funziona con riferimento di classe?

+1

Hai bisogno di un metodo virtuale per implementare il polimorfismo . . – kludg

risposta

7

Una costante di classe non tipizzata è una costante normale con aggiunta dell'ambito.
Una costante di classe tipizzata è in realtà una variabile di classe che non è possibile modificare.
Il problema è che le variabili di classe non sono virtuali.

Hallvard Vassbotn ha scritto su questo problema qui: Part 1, Part 2

Non è possibile accedere variabili di classe e le costanti di classe da un riferimento di classe perché la lingua non ha il supporto per le variabili di classe virtuali.
Quando si dice s:= TClass1.SomeConst il compilatore lo traduce in s:= SomeGlobalButHiddenConst prima di proseguire con il resto della compilazione.

class var e class const non sono altro che zucchero sintattico.
Come tale il collegamento tra class var/const e la classe effettiva esiste solo durante la compilazione, viene interrotto in fase di esecuzione, proprio come la cancellazione del tipo in Java.

RTTI anche non aiuta: Get constant fields from a class using RTTI
Credo che se si sta utilizzando D2007 l'unica opzione è quella di dichiarare una funzione virtuale che restituisce la costante che si desidera:

Pre opzione D2010: metodo virtuale

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; 

Questo è un triste stato di cose, ma non vedo un'altra opzione.

From Delphi 2010 onwards: utilizzare attributes
Una soluzione migliore è quella di utilizzare gli attributi, questi si può accedere utilizzando RTTI:

Il seguente codice funziona:

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

grazie Johan, la soluzione di classe classe sottoclasse funziona come un fascino. Molto interessante anche il post Vassbotn di Hallvard. Non riesco a immaginare che questo fosse un problema così vecchio ... e ancora irrisolto! : D – MtwStark

+1

@ MtwStark, ora che abbiamo gli attributi il ​​problema è in gran parte scomparso. – Johan

Problemi correlati