基于无约束泛型类型创建对象实例

11

我有一个不受限制的通用类型Atomic,它实现了一个初始化器(详见我之前的问题)。

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

现在我想编写一个简化的Initialize函数,该函数将从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 类型参数 'T' 没有类或接口约束

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

3个回答

14

您可以使用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 论坛中的 cjsalamon 发现的:Error in OtlSync XE2。)

只要我将结果强制转换回T类型(我已经修复了你的示例),它就完全按照我希望的方式工作了 - 谢谢! - gabr
欢迎!是的,需要进行类型转换。 - Ondrej Kelle
原来这段代码调用了错误的构造函数。Linas的“更新解决方案”在这方面效果更好。 - gabr

8

你可以使用新的Delphi Rtti来完成这项任务。给定解决方案的缺点是,如果构造函数未命名为Create,则无法工作。如果您需要始终使它工作,请枚举您的类型方法,检查它是否为构造函数并且没有参数,然后调用它。适用于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>;

谢谢,但XE2 Update 2的核心问题是如果T未标记为“class”约束,则TypeInfo(T)无法编译。 - gabr
不知道这个。这是一个“特性”还是一个漏洞? - Linas
我不确定,但我担心这是一项功能。 - gabr
看起来我关于TypeInfo(T)在XE2 Update 2上无法编译的说法是错误的。抱歉:( 我已经更新了TOndrej的答案,提供了在XE2 Update 2上可行的解决方案。 - gabr

0

如果我理解正确,泛型类型"T"是一个类。在这种情况下,只需声明:

Atomic< T: class > = class

使用嵌套的

代替平面的

Atomic< T > = class

这将告诉编译器T是一个类类型,因此您将能够使用构造函数和所有其他类类型的功能,而无需任何额外的解决方法。

如果我的基本假设理解错误,我深表歉意。


1
不,T有时是一个类,而有时则不是。 - gabr

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接