2011-12-02 77 views
11

我有无约束的泛型类型Atomic,它实现了一个初始化器(详见我的previous question)。基于不受约束的泛型类型创建对象实例

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

现在我想写简化这将需要在T型信息(前提是typeof运算(T)是tkClass),并创建新的实例(必要时)使用默认的构造函数初始化函数。

可悲的是,这种失败:

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; 

编译器报错E2571 Type parameter 'T' doesn't have class or interface constraint

我该如何欺骗编译器来创建类T的实例?

回答

5

您可以使用新的Delphi Rtti来完成这项任务。给定的解决方案的缺点是,如果构造函数没有被命名为Create,它将不起作用。如果你需要一直工作,只要枚举你的类型方法,检查它是否是一个构造函数并且有0个参数然后调用它。适用于Delphi XE。示例代码:

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; 

更新的解决方案:

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; 

,并调用它是这样的:

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

谢谢,但XE2 Update 2问题的核心是如果T没有标记'class'约束,则TypeInfo(T)将不会编译。 – gabr

+0

不知道这个。这是一个“功能”还是一个错误? – Linas

+0

我不确定,但恐怕这是一个功能。 – gabr

13

您可以使用GetTypeData获取类引用:

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

在Delphi XE2(并希望在接下来的版本),你可以这样做:

var 
    xInValue, xOutValue: TValue; 

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

(这相当规避方式被发现在OmniThreadLibrary论坛中使用cjsalamonError in OtlSync XE2。)

+0

假设我将结果回复给T(我已经修复了您的示例),它完全按照我的意愿工作 - 谢谢! – gabr

+0

欢迎!是的,有必要进行类型转换。 –

+0

原来(http://www.thedelphigeek.com/2011/12/creating-object-from-unconstrained.html?showComment=1323943258780#c4856703763737125607)这段代码调用了错误的构造函数。来自Linas的“更新解决方案”在这方面效果更好。 – gabr

0

如果我这样做是正确的泛型类型 “T” 是一类。在这种情况下,只是声明:

Atomic< T: class > = class 

,而不是平

Atomic<T> = class 

这将告诉编译器T是类类型,那么你就可以使用构造和所有类类型的其他功能,没有任何额外的解决方法。

如果我的理解在基本假设中是错误的,我很抱歉。

+1

不,T有时只是一个类,有时候不是。 – gabr