我想复制通用对象,但它的类型只能通过"类来获得"在运行时构造,因为源对象类型可能不同(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
动态实例化
不支持运行时的动态实例化。
这是我想做的吗?
答案 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;