2016-06-10 13 views
7

Ich verwende Delphi 2007, um ein altes Projekt beizubehalten. Ich habe ein Problem beim Zugriff auf Klassenkonstanten von einer Klassenreferenzvariablen. Ich erhalte immer die Elternklassenkonstante anstelle der untergeordneten .Zugriff auf Klassenkonstanten aus einer Klassenreferenzvariablen in Delphi

Angenommen, eine übergeordnete Klasse, einige untergeordnete Klassen, eine Klassenreferenz und schließlich ein const-Array zum Speichern der Klassenreferenzen für Schleifenzwecke.

einen Blick auf folgende einfache Programm:

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. 

Wenn es läuft, erhalte ich:

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

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

I 'KIND 1' und 'KIND 2' auch in Array-Schleife zu sehen erwartet hatte!

Kann mir jemand erklären, warum es nicht mit Klassenbezug funktioniert?

+1

Sie benötigen eine virtuelle Methode Polymorphismus zu implementieren . . – kludg

Antwort

7

Eine nicht typisierte Klassenkonstante ist eine normale Konstante, der einige Bereiche hinzugefügt wurden.
Eine typisierte Klassenkonstante ist wirklich eine Klassenvariable, die Sie nicht ändern können.
Das Problem ist, dass die Klassenvariablen nicht virtuell sind.

Hallvard Vassbotn hat hier über dieses Thema geschrieben: Part 1, Part 2

Sie können keine Klassenvariablen und Klassenkonstanten von einer Klassenreferenz zugreifen, da die Sprachunterstützung für Variablen virtuelle Klasse nicht.
Wenn Sie s:= TClass1.SomeConst sagen, übersetzt der Compiler das in s:= SomeGlobalButHiddenConst, bevor Sie mit dem Rest der Kompilierung fortfahren.

class var und class const sind nichts anderes als syntaktischer Zucker.
Als solche die Verbindung zwischen der class var/const und der tatsächlichen Klasse existiert nur während der Kompilierzeit, es ist kaputt kommen Laufzeit, ähnlich wie Typ-Löschen in Java.

RTTI auch nicht helfen: Get constant fields from a class using RTTI
Ich denke, wenn man D2007 Ihre einzige Option verwenden wird eine virtuelle Funktion zu erklären, dass der Konstante zurück Sie wollen:

Pre D2010 Option: virtuelle Methode

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; 

Dies ist ein trauriger Zustand, aber ich sehe keine andere Option.

From Delphi 2010 onwards: Verwenden Sie attributes
Eine bessere Option Attribute zu verwenden ist, können diese Ihnen den Zugriff RTTI mit:

Der folgende Code funktioniert:

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

danke Johan, funktioniert die Unterklasse Klassenfunktion wie ein Charme. Sehr interessant auch die Hallvard Vassbotn Post. Ich kann mir nicht vorstellen, dass dies ein so altes Problem war ... und immer noch ungelöst! : D – MtwStark

+1

@MtwStark, jetzt, da wir Attribute haben, ist das Problem weitgehend verschwunden. – Johan