2011-12-02 12 views
11

Tengo el tipo genérico atómico sin restricciones que implementa un inicializador (detalles en mi previous question).Creación de instancia de objeto basada en el tipo genérico no restringido

type 
    Atomic<T> = class 
    type TFactory = reference to function: T; 
    class function Initialize(var storage: T; factory: TFactory): T; 
    end; 

Ahora quiero escribir la función de inicialización que tendría la información de tipo de T (siempre que typeof (T) es tkClass) y crear nueva instancia (si fuera necesario) con el constructor por defecto simplificado.

Lamentablemente, esto no funciona:

class function Atomic<T>.Initialize(var storage: T): T; 
begin 
    if not assigned(PPointer(@storage)^) then begin 
    if PTypeInfo(TypeInfo(T))^.Kind <> tkClass then 
     raise Exception.Create('Atomic<T>.Initialize: Unsupported type'); 
    Result := Atomic<T>.Initialize(storage, 
     function: T 
     begin 
     Result := TClass(T).Create; // <-- E2571 
     end); 
    end; 
end; 

compilador informa de error E2571 Type parameter 'T' doesn't have class or interface constraint.

¿Cómo puedo engañar al compilador para crear una instancia de la clase T?

Respuesta

5

Puede usar el nuevo Delphi Rtti para hacer esta tarea. El inconveniente de la solución dada es que no funcionará si el constructor no se nombra como Crear. Si necesita hacer que funcione todo el tiempo, simplemente enumere sus métodos de tipo, verifique si es un constructor y tenga 0 parámetros y luego inícielo. Trabaja en Delphi XE. Código de ejemplo:

class function TTest.CreateInstance<T>: T; 
var 
    AValue: TValue; 
    ctx: TRttiContext; 
    rType: TRttiType; 
    AMethCreate: TRttiMethod; 
    instanceType: TRttiInstanceType; 
begin 
    ctx := TRttiContext.Create; 
    rType := ctx.GetType(TypeInfo(T)); 
    AMethCreate := rType.GetMethod('Create'); 

    if Assigned(AMethCreate) and rType.IsInstance then 
    begin 
    instanceType := rType.AsInstance; 

    AValue := AMethCreate.Invoke(instanceType.MetaclassType, []);// create parameters 

    Result := AValue.AsType<T>; 
    end; 
end; 

solución Actualizado:

class function TTest.CreateInstance<T>: T; 
var 
    AValue: TValue; 
    ctx: TRttiContext; 
    rType: TRttiType; 
    AMethCreate: TRttiMethod; 
    instanceType: TRttiInstanceType; 
begin 
    ctx := TRttiContext.Create; 
    rType := ctx.GetType(TypeInfo(T)); 
    for AMethCreate in rType.GetMethods do 
    begin 
    if (AMethCreate.IsConstructor) and (Length(AMethCreate.GetParameters) = 0) then 
    begin 
     instanceType := rType.AsInstance; 

     AValue := AMethCreate.Invoke(instanceType.MetaclassType, []); 

     Result := AValue.AsType<T>; 

     Exit; 
    end; 
    end; 
end; 

y lo llaman así:

var 
    obj: TTestObj; 
begin 
    obj := TTest.CreateType<TTestObj>; 
+0

Gracias, pero el núcleo del problema con la Actualización 2 de XE2 es que TypeInfo (T) no se compilará si T no está marcado con la restricción 'clase'. – gabr

+0

No sabía esto. ¿Es esta una característica o un error? – Linas

+0

No estoy seguro, pero me temo que es una característica. – gabr

13

Puede utilizar GetTypeData para obtener la referencia de clase:

Result := T(GetTypeData(PTypeInfo(TypeInfo(T)))^.ClassType.Create); 

En Delphi XE2 (y esperamos que en próximas versiones), que puede hacer:

var 
    xInValue, xOutValue: TValue; 

xInValue := GetTypeData(PTypeInfo(TypeInfo(T)))^.ClassType.Create; 
xInValue.TryCast(TypeInfo(T), xOutValue); 
Result := xOutValue.AsType<T>; 

(De esta manera en lugar de eludir fue descubierto usado cjsalamon en el foro OmniThreadLibrary: Error in OtlSync XE2.)

+0

Siempre que devuelva el resultado a T (he reparado su ejemplo) funciona exactamente como yo deseaba, ¡gracias! – gabr

+0

¡Bienvenido! Sí, es necesario encasillar. –

+0

Resultó (http://www.thedelphigeek.com/2011/12/creating-object-from-unconstrained.html?showComment=1323943258780#c4856703763737125607) que este código llama al constructor equivocado. La "solución actualizada" de Linas funciona mejor en ese sentido. – gabr

0

Si lo hizo bien, el tipo genérico "T" es una clase . En este caso, basta con declarar:

Atomic< T: class > = class 

en lugar de la plana

Atomic<T> = class 

Esto le dirá al compilador que T es un tipo de clase, por lo que será capaz de utilizar el constructor y todos las otras características del tipo de clase sin ninguna solución adicional.

Si mi comprensión era incorrecta en la suposición base, me disculpo.

+1

No, T solo es a veces una clase, otras veces no lo es. – gabr

Cuestiones relacionadas