【问题标题】:Delphi copy generic object with unknown base type at compile timeDelphi 在编译时复制具有未知基类型的通用对象
【发布时间】:2015-12-29 12:22:34
【问题描述】:

我想复制通用对象,但它的类型只能在运行时通过“类”构造获得,因为源对象类型可能不同(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

动态实例化

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

这是我想做的吗?

【问题讨论】:

  • 除了您的语法错误之外,您尝试的通常是不可能的。您可能没有在编译时实际实例化具体类型 TDCollection&lt;Cls&gt;。与其问如何解决问题,不如问如何解决问题。这样会带来有用的帮助。
  • 语法已更正。谢谢你的回答就足够了。我可以用另一种方式解决我的问题,但我只是想知道这是否可行。
  • 我所说的语法错误的意思是 Cls 在运行时是未知的,并且必须是泛型实例化。您可以使用 RTTI 创建实例,但必须实例化类型。
  • 我之所以没有尝试向您展示如何操作,是因为我怀疑您的处理方式完全错误。很明显,你并不真正知道自己在做什么。您应该正视这一点并寻求帮助,为您的问题找到一个好的解决方案,而不是询问如何实施您的解决方案来解决我打赌是错误解决方案的问题。
  • @David 如果你愿意,让我们在我承认的答案下方进一步讨论。

标签: delphi generics


【解决方案1】:

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

type
  TItemContainerclass = class of TItemContainer;

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

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

function TItemContainer<T>.ItemClass: TItemClass;
begin
  Result := T;
end;

请注意,这只会创建一个新的 instance 而不是源的 副本,但是由于您的代码没有显示任何复制对象的尝试,并且仅创建一个新实例,我认为这是您的预期结果。

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

【讨论】:

  • TItemContainerclass(Source.ClassType).Create 有效,但它不是泛型的解决方案(使用&lt; ... &gt;
  • 这里提出的问题不需要泛型的解决方案。
  • 不起作用。 Source.ClassType 报告无法访问的值,并且在调用 TItemContainerclass(Source.ClassType).Create; 时发生异常。
  • 那么“Source”很可能是“nil”或未初始化。
  • @tk_ 你的代码有缺陷。此代码工作正常。正如肯所说,您没有传递有效的Source
【解决方案2】:

线

Cls := Source.ItemClass;

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

例如,这些将编译:

Result := TItemContainer<TSpecificItem>.Create; 

Result := TItemContainer<TEvenMoreSpecificItem>.Create; 

但不是这个

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

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

【讨论】:

  • 好的。这意味着我可以在这里使用的唯一方法是if Cls = TSpecificItem then Result := TItemContainer&lt;TSpecificItem&gt;.Create; else if Cls = TEvenMoreSpecificItem then Result := TItemContainer&lt;TEvenMoreSpecificItem&gt;.Create; else if Cls =...
  • 所以对于每个子类我需要单独的构造函数调用。不过,这似乎是上述问题的唯一解决方案。
  • @tk 正如我所解释的,只要类型已被实例化,就可以使用 rtti 来完成。如果您询问导致问题的问题会好得多。
  • @David 但这就是问题所在。我需要将一个容器的项目分配给另一个精确的副本。没有泛型没问题,有泛型这个问题。我不知道你对 rtti 解决方案是什么意思。
  • 你没有分配任何东西。目前我们正在研究如何实例化容器。只要类型被实例化,就可以使用 rtti 来完成。我猜你不知道实例化泛型类型意味着什么。这就是你不理解我的原因。如果你不明白这会让生活变得艰难。阅读文档将弥补知识缺陷。如果你不告诉我们潜在的问题,我认为你不会从中得到很多。
【解决方案3】:

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

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

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

还有一个技巧可能对您有用:如何复制各种对象。例如,您有公共类 TAnimal 及其后代:TCat 和 TDog。您将它们存储在 TItemContainer 中,这就是您可以执行并普遍处理它们的全部继承点。现在,您想要实现创建此容器的副本,并且在编译时您不知道哪些元素是狗,哪些是猫。标准方法是在 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 派生您的类并在您需要的任何地方实现分配过程(非常有用的东西),然后一劳永逸地编写一个复制构造函数:

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

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

【讨论】:

  • T 的类型在编译时是未知的,所以这一切都无关紧要,-1
猜你喜欢
  • 2023-04-03
  • 2010-11-11
  • 1970-01-01
  • 1970-01-01
  • 2013-05-17
  • 2017-09-16
  • 2011-08-13
  • 2015-09-15
  • 2016-01-17
相关资源
最近更新 更多