2009-04-26 6 views
20

Wie kann ich eine Instanz eines Objekts mithilfe einer Klassenreferenz erstellen und sicherstellen, dass der Konstruktor ausgeführt wird?Wie kann ich ein Delphi-Objekt aus einer Klassenreferenz erstellen und die Ausführung des Konstruktors sicherstellen?

type 
    TMyClass = class(TObject) 
    MyStrings: TStrings; 
    constructor Create; virtual; 
    end; 

constructor TMyClass.Create; 
begin 
    MyStrings := TStringList.Create; 
end; 

procedure Test; 
var 
    Clazz: TClass; 
    Instance: TObject; 
begin 
    Clazz := TMyClass; 
    Instance := Clazz.Create; 
end; 

Antwort

23

verwenden:

type 
    TMyClass = class(TObject) 
    MyStrings: TStrings; 
    constructor Create; virtual; 
    end; 
    TMyClassClass = class of TMyClass; // <- add this definition 

constructor TMyClass.Create; 
begin 
    MyStrings := TStringList.Create; 
end; 

procedure Test; 
var 
    Clazz: TMyClassClass; // <- change TClass to TMyClassClass 
    Instance: TObject; 
begin 
    Clazz := TMyClass; // <- you can use TMyClass or any of its child classes. 
    Instance := Clazz.Create; // <- virtual constructor will be used 
end; 

Alternativ Sie

In diesem Codebeispiel wird der Konstruktor von TMyClass nicht aufgerufen werden kann einen Typ-Casts zu TMyClass (anstelle von "Klasse von TMyClass") verwenden.

+1

Ok, wenn ich das richtig verstehe, heißt das, wenn ich eine generische Objektfactory mit Delphi erstellen will, muss ich "class of TMyClass" einer Variablen zuweisen - aber das scheint nicht möglich. – mjn

+1

Wenn Sie ein Objekt eines bestimmten Typs erstellen möchten, benötigen Sie Informationen zum Klassentyp. Wenn Sie keine Klasseninformationen haben, können Sie kein Objekt dieses Typs erstellen. Ziemlich offensichtlich;) – Alex

6

Ihr Code leicht geändert:

type 
    TMyObject = class(TObject) 
    MyStrings: TStrings; 
    constructor Create; virtual; 
    end; 
    TMyClass = class of TMyObject; 

constructor TMyObject.Create; 
begin 
    inherited Create; 
    MyStrings := TStringList.Create; 
end; 

procedure Test; 
var 
    C: TMyClass; 
    Instance: TObject; 
begin 
    C := TMyObject; 
    Instance := C.Create; 
end; 
10

Überprüfen Sie, ob das Überschreiben von AfterConstruction eine Option ist.

+1

Sehr gute Idee, es ist eine virtuelle Methode in TObject, so dass ich keine synthetische neue Root-Klasse hinzufügen muss. +1 für diese Idee. – mjn

+0

Sehr nützlich in der Tat! –

18

Alexanders Lösung ist eine feine, aber in bestimmten Situationen nicht ausreichend. Angenommen, Sie möchten eine TClassFactory-Klasse einrichten, in der TClass-Referenzen zur Laufzeit und eine beliebige Anzahl später abgerufener Instanzen gespeichert werden können.

Eine solche Klassenfabrik würde niemals etwas über die tatsächlichen Typen der Klassen wissen, die sie enthält, und kann sie daher nicht in ihre entsprechenden Meta-Klassen einordnen. Um in solchen Fällen die richtigen Konstruktoren aufzurufen, funktioniert der folgende Ansatz.

Zuerst brauchen wir eine einfache Demo-Klasse (die öffentlichen Felder nicht stören, es ist nur zu Demonstrationszwecken).

interface 

uses 
    RTTI; 

type 
    THuman = class(TObject) 
    public 
    Name: string; 
    Age: Integer; 

    constructor Create(); virtual; 
    end; 

implementation 

constructor THuman.Create(); 
begin 
    Name:= 'John Doe'; 
    Age:= -1; 
end; 

Jetzt instanziieren wir ein Objekt vom Typ THuman rein per RTTI und mit dem korrekten Konstruktoraufruf.

procedure CreateInstance(); 
var 
    someclass: TClass; 
    c: TRttiContext; 
    t: TRttiType; 
    v: TValue; 
    human1, human2, human3: THuman; 
begin 
    someclass:= THuman; 

    // Invoke RTTI 
    c:= TRttiContext.Create; 
    t:= c.GetType(someclass); 

    // Variant 1a - instantiates a THuman object but calls constructor of TObject 
    human1:= t.AsInstance.MetaclassType.Create; 

    // Variant 1b - same result as 1a 
    human2:= THuman(someclass.Create); 

    // Variant 2 - works fine 
    v:= t.GetMethod('Create').Invoke(t.AsInstance.MetaclassType,[]); 
    human3:= THuman(v.AsObject); 

    // free RttiContext record (see text below) and the rest 
    c.Free; 

    human1.Destroy; 
    human2.Destroy; 
    human3.Destroy; 
end; 

Sie werden feststellen, dass die Objekte „human1“ und „human2“ auf Null initialisiert worden ist, das heißt, Name = ‚‘ und Alter = 0, was nicht das, was wir wollen. Das Objekt human3 enthält stattdessen die im Konstruktor von THuman angegebenen Standardwerte.

Beachten Sie jedoch, dass diese Methode erfordert, dass Ihre Klassen Konstruktormethoden mit nicht-Parametern haben. All das oben Gesagte wurde nicht von mir erdacht, sondern brillant und viel ausführlicher erläutert (z. B. der c.Freie Teil) in Rob Love's Tech Corner.

0

Sie können eine abstrakte Methode in der Basisklasse erstellen und sie im Konstruktor aufrufen und sie in untergeordneten Klassen überschreiben, die beim Erstellen aus der Klassenreferenz ausgeführt werden sollen.

+0

Das ist nicht genau das, wonach die Frage fragt. – bummi

+0

Ich wurde mit dem gleichen Problem konfrontiert, wenn ich die Klassenreferenz im Konstruktor eines Listenmanagers übergab, welche Art von Kind es war die Liste auffüllen, und wenn die Liste die Kinder, nur die Eltern-Basisklasse der Kinder-Konstruktor erstellt hieß ... der Konstrukteur von Kinderartikeln war nicht ...die einzige Lösung für mich, weil Konstruktor Parameter haben muss, eine abstrakte Methode deklarieren, im übergeordneten Konstruktor aufrufen und in untergeordneten Klassen überschreiben sollte. Diese Problemumgehung kann dazu beitragen, dass jemand mit dem gleichen Problem konfrontiert wird ... Mit freundlichen Grüßen! – alijunior

Verwandte Themen