Delphi在编译时复制具有未知基类型的通用对象

时间:2015-12-29 12:22:34

标签: delphi generics

我想复制通用对象,但它的类型只能通过"类来获得"在运行时构造,因为源对象类型可能不同(TItem或TSpecificItem等):

type
  TItem = class
  //...
    procedure Assign(Source: TItem);virtual; abstract; //edit
  end;

  TSpecificItem = class(TItem)
  //...
  end;

  TEvenMoreSpecificItem = class(TSpecificItem)
  //...
  end;

  TItemClass = class of TItem;

  TItemContainer = class
    FItems: TObjectList<TItem>; //edit
    procedure Assign(Source: TObject); //edit
    function GetItem(Index: Integer): TItem; inline; //edit
    procedure SetItem(Index: Integer; Item: TItem); inline; //edit
    function Count: Integer; //edit;
    function ItemClass: TItemClass; virtual; abstract;
    property Items[Index: Integer]: TItem read GetItem write SetItem; //edit
  end;

  TItemContainer<T: TItem> = class(TItemContainer)
  //...
    function GetItem(Index: Integer): T; inline; //edit
    procedure SetItem(Index: Integer; Item: T); inline; //edit
    function ItemClass: TItemClass; override;
    property Items[Index: Integer]: T read GetItem write SetItem; default; //edit
  end;

//start of edit
function TItemContainer.Count: Integer;
begin
  Result := FItems.Count;
end;

function TItemContainer.GetItem(Index: Integer): TItem;
begin
  Result := FItems[Index];
end;

procedure TItemContainer.SetItem(Index: Integer; Item: TItem);
begin
  FItems[Index].Assign(Item);
end;

procedure TItemContainer.Assign(Source: TObject);
var
  I: Integer;
  Item: TItem;
  Cls: TClass;
begin
  if Source is TItemContainer then
  begin
    FItems.Clear;
    for I := 0 to TItemContainer(Source).Count - 1 do
    begin
      Item := TItemContainer(Source).Items[I];
      Cls := Item.ClassType;
      Item := TItemClass(Cls).Create;
      Item.Assign(TItemContainer(Source).Items[I]);
      FItems.Add(Item);
    end;
  end;
end;

function TItemContainer<T>.GetItem(Index: Integer): T;
begin
  Result := T(inherited GetItem(Index));
end;

procedure TItemContainer<T>.SetItem(Index: Integer; Item: T);
begin
  inherited SetItem(Index, Item);
end;
//end of edit

function TItemContainer<T>.ItemClass: TItemClass;
begin
  Result := TItemClass(GetTypeData(PTypeInfo(TypeInfo(T)))^.ClassType);
end;

function CopyGenericObject(Source: TItemContainer): TItemContainer;
var
  Cls: TItemClass;
begin
  Cls := Source.ItemClass;
  Result := TItemContainer<Cls>.Create; // compiler reports error "incompatible types"
  Result.Assign(Source);
end;

// edit:
procedure DoCopy;
var
  Source: TItemContainer<TEvenMoreSpecificItem>;
  Dest: TItemContainer;
begin
  Source := TItemContainer<TEvenMoreSpecificItem>.Create; // for example
  //add some items to Source
  Dest := CopyGenericObject(Source);
  //use the result somewhere
end;

我必须使用Delphi XE。

我找到了 http://docwiki.embarcadero.com/RADStudio/XE6/en/Overview_of_Generics

  

动态实例化

     

不支持运行时的动态实例化。

这是我想做的吗?

3 个答案:

答案 0 :(得分:2)

如果我理解得很好,你要找的是实现一个例程,该例程将创建与给定源相同类型的类的实例。这可以这样做:

>=

此外,您可以将ItemClass例程简化为

type
  TItemContainerclass = class of TItemContainer;

function CopyGenericObject(Source: TItemContainer): TItemContainer;
begin
  Result := TItemContainerclass(Source.ClassType).Create; 
end;

请注意,这只会创建新的实例而不是源的副本,但由于您的代码未显示任何复制对象的尝试,因此创建一个新实例,我认为这是你想要的结果。

注意:这在Delphi 10中有效,我无法访问XE来测试它。

答案 1 :(得分:1)

该行

Cls := Source.ItemClass;

仅在运行时创建TItemClass实例。对于泛型,编译器需要在编译时知道类型。在不知情的情况下,编译器无法生成实现特定TItemContainer<Cls>的二进制代码。或者,换句话说,Cls不能是变量,它必须是特定的类类型,在编译时已知。

例如,这些将编译:

Result := TItemContainer<TSpecificItem>.Create; 

Result := TItemContainer<TEvenMoreSpecificItem>.Create; 

但不是这个

Result := TItemContainer</* type will be known later */>.Create; 

因为编译器以后无法返回并根据Cls的实际类型完成二进制应用程序代码。

答案 2 :(得分:-1)

您可以将CopyGenericObject函数作为通用对象的方法而不是独立函数:

TItemContainer<T: TItem> = class(TItemContainer)
  ...
  function Copy: TItemContainer<T>;
end;

在这种情况下,它在编译时“知道”,创建什么类只是因为在编译器完成其工作之后现在有几个类(每个实例化类型一个),每个类都自己复制。

还有一个技巧可能对您的情况有用:如何复制各种对象。例如,您有公共类TAnimal及其后代:TCat和TDog。您将它们存储在TItemContainer中,这是您可以执行的整个继承点并且通常会对它们进行处理。现在,您想要实现创建此容器的副本,并且您在编译时不知道哪些元素将是狗,哪些元素将是猫。 Standart方法是在TAnimal中定义抽象函数Copy:

TAnimal = class
  public
    ...
    function Copy: TAnimal; virtual; abstract;
end;

然后在每个后代实现它,那么你可以像这样复制你的TItemContainer:

    function TItemContainer<T>.Copy: TItemContainer<T>;
    var i: T;
    begin
      Result:=TItemContainer<T>.Create;
      for i in Items do 
 //I don't know exact structure of your container,   
 //maybe that's more like
 // for j:=0 to Count-1 do begin
 //   i:=Items[j];
 //but I hope it's obvious what happens here
        Result.Add(i.copy as T);
    end;

所以如果你有猫的容器,那么i.copy将返回TAnimal(但实际上是一只猫),它最终将被转换为TCat。它有效,但有点难看。

在delphi中,我提出了更好的解决方案:将此copy作为构造函数,而不是函数:

TAnimal = class
  public
    ...
    constructor Copy(source: TAnimal); virtual;
end;

在这种情况下,复制容器是这样的:

function TItemContainer<T>.Copy: TItemContainer<T>;
var i,j: T;
begin
  Result:=TItemContainer<T>.Create;
  for i in Items do
    Result.Add(T.Copy(i));
end;

没有额外的铸造,这是好的。更重要的是,你可以从TPersistent派生你的类,并在你需要的地方实现Assign程序(非常有用的东西),然后一劳永逸地写一个拷贝构造函数:

TAnimal = class(TPersistent)
  public
    constructor Copy(source: TPersistent); //or maybe source: TAnimal
end;

//implementation
constructor TAnimal.Copy(source: TPersistent);
begin
  Create;
  Assign(source);
end;