2011-12-02 13 views
11

Mam nieograniczony typ generyczny Atomic, który implementuje inicjalizator (szczegóły w moim previous question).Tworzenie instancji obiektu na podstawie nieograniczonego typu generycznego

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

Teraz chcę napisać funkcję Initialize uproszczone, które zajęłoby się z informacjami od typu T (pod warunkiem, że typeof (T) jest tkClass) i utworzyć nową instancję (jeśli to konieczne) z domyślnego konstruktora.

Niestety, to się nie powiedzie:

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; 

kompilator zgłasza błąd E2571 Type parameter 'T' doesn't have class or interface constraint.

Jak mogę oszukać kompilator, aby utworzyć wystąpienie klasy T?

Odpowiedz

5

Możesz użyć nowej Delphi Rtti, aby wykonać to zadanie. Wadą danego rozwiązania jest to, że nie zadziała, jeśli konstruktor nie jest nazwany jako Utwórz. Jeśli chcesz, aby działał cały czas, po prostu wylicz swoje metody typu, sprawdź, czy jest to konstruktor i czy masz 0 parametrów, a następnie wywołaj go. Działa w Delphi XE. Przykładowy kod:

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; 

Updated rozwiązanie:

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; 

i nazwać tak:

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

Dzięki, ale sednem problemu z XE2 Update 2 jest to, że TypeInfo (T) nie skompiluje się, jeśli T nie jest oznaczone ograniczeniem "class". – gabr

+0

Nie wiedziałem tego. Czy jest to "funkcja" lub błąd? – Linas

+0

Nie jestem pewien, ale obawiam się, że to funkcja. – gabr

13

Można użyć GetTypeData aby uzyskać odniesienie Klasa:

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

w Delphi XE2 (i miejmy nadzieję, że w następnych wydaniach), można zrobić:

var 
    xInValue, xOutValue: TValue; 

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

(W ten sposób raczej obejścia została odkryta przez użycie cjsalamon na forum OmniThreadLibrary: Error in OtlSync XE2.)

+0

Pod warunkiem, że wyślę wynik do T (poprawiłem twój przykład) działa dokładnie tak, jak chciałem - dzięki! – gabr

+0

Witamy! Tak, konieczne jest typecast. –

+0

Okazało się (http://www.thedelphigeek.com/2011/12/creating-object-od-unconstrained.html?showComment=1323943258780#c4856703763737125607), że ten kod wywołuje nieprawidłowy konstruktor. "Zaktualizowane rozwiązanie" od Linas działa lepiej pod tym względem. – gabr

0

Jeśli mam rację, typ rodzajowy "T" jest klasą . W tym przypadku, po prostu zadeklarować:

Atomic< T: class > = class 

zamiast płaskiej

Atomic<T> = class 

Dzięki temu dowiesz się, że kompilator T jest typu klasy, dzięki czemu będziemy w stanie użyć konstruktora i wszystko inne cechy klasy bez dodatkowych obejść.

Jeśli moje zrozumienie było błędne w podstawowym założeniu, przepraszam.

+1

Nie, T to tylko czasami klasa, innym razem tak nie jest. – gabr

Powiązane problemy