2011-12-02 9 views
11

Ho un tipo generico non vincolato Atomic che implementa un inizializzatore (dettagli nel mio previous question).Creazione istanza di un oggetto basata su tipo generico non vincolato

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

Ora voglio scrivere semplificato funzione Initialize che avrebbe preso le informazioni sul tipo di T (a condizione che typeof (T) è tkClass) e creare nuova istanza (quando necessario) con il costruttore di default.

Purtroppo, questo non funziona:

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; 

compilatore segnala l'errore E2571 Type parameter 'T' doesn't have class or interface constraint.

Come posso ingannare il compilatore per creare un'istanza di classe T?

risposta

5

È possibile utilizzare il nuovo Delphi Rtti per eseguire questa operazione. Lo svantaggio della soluzione data è che non funzionerà se il costruttore non viene nominato come Crea. Se è necessario farlo funzionare sempre, basta enumerare i metodi del tipo, controllare se si tratta di un costruttore e avere 0 parametri e quindi richiamarlo. Funziona con Delphi XE. Esempio di codice:

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; 

soluzione Aggiornato:

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; 

E chiamare in questo modo:

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

Grazie, ma il nocciolo del problema con XE2 Update 2 è che TypeInfo (T) non verrà compilato se T non è contrassegnato con il vincolo 'classe'. – gabr

+0

Non lo sapevo. È una "caratteristica" o un bug? – Linas

+0

Non ne sono sicuro, ma temo sia una funzionalità. – gabr

13

È possibile utilizzare GetTypeData per ottenere il riferimento di classe:

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

In Delphi XE2 (e si spera nelle prossime releases), si può fare:

var 
    xInValue, xOutValue: TValue; 

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

(In questo modo piuttosto aggirare è stato scoperto utilizzato cjsalamon nel forum OmniThreadLibrary: Error in OtlSync XE2.)

+0

A condizione che io restituisca i risultati a T (ho corretto il tuo esempio) funziona esattamente come volevo - grazie! – gabr

+0

Benvenuto! Sì, è necessario digitare. –

+0

Si è scoperto (http://www.thedelphigeek.com/2011/12/creating-object-from-unconstrained.html?showComment=1323943258780#c4856703763737125607) che questo codice chiama il costruttore errato. "La soluzione aggiornata" di Linas funziona meglio a tale riguardo. – gabr

0

Se ho capito bene, il tipo generico "T" è una classe . In questo caso, basta dichiarare:

Atomic< T: class > = class 

al posto del piatto

Atomic<T> = class 

Questo dirà al compilatore che T è un tipo di classe, così sarete in grado di utilizzare il costruttore e tutti le altre caratteristiche del tipo di classe senza alcuna soluzione alternativa.

Se la mia comprensione era errata nell'ipotesi di base, mi scuso.

+1

No, T è solo qualche volta una classe, altre volte non lo è. – gabr

Problemi correlati