2009-04-26 19 views
20

¿Cómo puedo crear una instancia de un objeto utilizando una referencia de clase y asegurarme de que se ejecuta el constructor?¿Cómo puedo crear un objeto Delphi a partir de una referencia de clase y asegurar la ejecución del constructor?

En este ejemplo de código, no se llamará al constructor de TMyClass:

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; 

Respuesta

23

Utilice esta:

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; 

alternativa, puede usar un tipo de conversión a TMyClass (en lugar de "clase de TMyClass").

+1

Ok, si entiendo correctamente esto significa que si quiero construir una fábrica de objetos genéricos con Delphi, necesito asignar "clase de TMyClass" a una variable, pero esto no parece posible. – mjn

+1

Si desea construir un objeto de cierto tipo, entonces necesita tener información de tipo de clase. Si no tiene información de clase, no puede construir un objeto de este tipo. Bastante obvio;) – Alex

6

Su código ligeramente modificado:

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

Compruebe si la anulación AfterConstruction es una opción.

+1

Muy buena idea, es un método virtual en TObject, así que no necesito agregar ninguna nueva clase de raíz sintética. +1 por esta idea – mjn

+0

¡Muy útil de hecho! –

18

La solución de Alexander es buena pero no es suficiente en ciertas situaciones. Supongamos que desea configurar una clase TClassFactory donde las referencias de TClass puedan almacenarse durante el tiempo de ejecución y un número arbitrario de instancias recuperadas posteriormente.

Tal clase de fábrica nunca sabría nada sobre los tipos reales de las clases que posee y, por lo tanto, no puede convertirlas en sus metacategorías. Para invocar a los constructores correctos en tales casos, el siguiente enfoque funcionará.

En primer lugar, necesitamos una clase de demostración simple (no importa los campos públicos, es sólo para fines de demostración).

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; 

Ahora instanciamos un objeto de tipo THuman puramente por RTTI y con la llamada de constructor correcta.

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; 

Usted encontrará que los objetos "human1" y "human2" se han inicializado a cero, es decir, Name = '' y la Edad = 0, lo cual no es lo que queremos. El objeto human3 en cambio contiene los valores predeterminados proporcionados en el constructor de THuman.

Tenga en cuenta, sin embargo, que este método requiere que sus clases tengan métodos de construcción sin parámetros. Todo lo anterior no fue concebido por mí, sino que se explicó brillantemente y con mucho más detalle (por ejemplo, la parte c.Free) en Rob Love's Tech Corner.

0

Puede crear un método abstracto en la clase base, invocarlo en el constructor y anular en clases secundarias para que se ejecute cuando se cree a partir de la referencia de clase.

+0

Esto no es exactamente lo que la pregunta está pidiendo. – bummi

+0

Me enfrenté con el mismo problema cuando paso la referencia de clase en el constructor de algún administrador de lista, especificando qué clase de hijo era poblar la lista, y cuando la lista creaba los elementos secundarios, solo la clase base principal del contructor de elementos secundarios se llamaba ... el contructor de elementos infantiles no era ...La única solución para mí, porque el constructor debe tener parámetros, fue declarar un método abstracto, llamarlo en el constructor padre y anular en clases secundarias. Esta solución puede ayudar a alguien a enfrentar nuestro mismo problema ... ¡Saludos! – alijunior

Cuestiones relacionadas