DELPHI:泛型和多态性

13

这个问题已经被问了很多次,但我还没有找到答案。

请有人为我澄清一些事情。 使用:Delphi XE2

我有一个非常大的BaseObject,几乎用于所有事情。 除此之外,我还有一个通用列表 - BaseList。

声明如下:

TBaseObject = class
... a lot of properties and methods ...
end;

TBaseList<T: TBaseObject> = class(TObjectList<T>)
... some properties and methods ... 
end;

我最近尝试将非常老旧的TStringList声明中使用的Objects[]属性更改为更加通用的泛型列表TObjectList。

但我遇到了一些问题。 BaseUnit是一个文件...每次我派生我的BaseObject时,我也会制作一个专门的列表来跟随它。

所以我会像这样做:

TCustomer = class(TBaseObject)
... customer related stuff ...
end;

TCustomerList<T: TCustomer> = class(TBaseList<T>)
... customer list specific stuff ...
end;

但现在我希望有一个对象来包含一个列表——可以容纳任何对象。我想我可以这样做:

TControlObject = class(TBaseobject)
  FGenList: TBaseList<TBaseObject>; 
end;

由于BaseList和BaseObject是我的继承结构的顶端,我认为这样的列表可以容纳任何我能想到的列表。

但我觉得我在这里失败了... 一个TBaseList<TBaseobject>与TCustomerList<TCustomer>不可比较... 即使TCustomerList和TCustomer都是从我的基类派生而来。

我希望能够在baselist中使用泛型来实例化新对象。 例如,在populate方法中使用T.Create。

以下是完整继承结构的示例:

Base Unit;
TBaseObject = class
end;
TBaseList<T:TBaseObject> = class(TObjectList<T>)
end;

CustomCustomer Unit;
TCustomCustomer = class(TBaseObject) 
end;
TCustomCustomerList<T:TCustomCustomer> = class(TBaseList<T>)
end;

Customer Unit;
TCustomer = class(TCustomCustomer)
end;
TCustomerList<T:TCustomer> = class(TCustomCustomerList<T>)
end;

CustomPerson Unit;
TCustomPerson = class(TBaseObject) 
end;
TCustomPersonList<T:TCustomPerson> = class(TBaseList<T>)
end;

Person Unit;
TPerson = class(TCustomPerson)
end;
TPersonList<T:TPerson> = class(TCustomPersonList<T>)
end;

考虑到上述的层次结构,为什么我不能:
var    
  aList : TBaseList<TBaseObject>;  // used as a list parameter for methods
  aPersonList : TPersonList<TPerson>;
  aCustomerList : TCustomerList<TCustomer>;
begin
  aPersonList := TPersonList<TPerson>.Create;
  aCustomerList := TCustomerList<TCustomer>.Create;

  aList := aCustomerList;  <-- this FAILS !!  types not equal ..

end;

调用处理所有列表基类的过程会以相同方式失败...

Procedure LogStuff(SomeList : TBaseList<TBaseObject>)
begin
  writeln(Format( 'No. Elements in list : %d',[SomeList.Count]));
end;

有人能打我一拳并告诉我这里出了什么问题吗?

您能提供一些代码,展示这种方法的失败吗?我不确定我是否理解正确。 - Uwe Raabe
你是想将 TCustomerList 分配给 FGenList,还是想将 TCustomer 存储在 FGenList 中?(虽然我可能无法回答,但这可能会澄清你试图实现什么)。另外,为什么要声明不同的列表类型?泛型的重点是您不再需要它们来保证类型安全。TList<TCustomer>.Create 与 TList<TProduct>.Create 类型不兼容。 - Marjan Venema
好的。我是老派的面向对象编程员 - 泛型对我来说也是新的。我使用继承的原因是由于特定的方法/属性...但如果我可以通过聚合/组合获得相同的效果...那么我会尝试。另一个原因是 - 我已经编写了20年的继承代码 - 所以它已经深入我的骨髓中 :-) - Bimmer_R
如果我能看到任何实际需要使用协方差而不是使用其他方法,我会喜欢这个问题。否则,这只是一种C#-me-too主义。 - Warren P
@MarjanVenema: 你几天前提出的建议似乎是一个不错的主意。但是实际尝试后揭示了我所遇到的问题。FList := TBaseList<TPerson>.Create 是不可能的 - 因为 FList 属于另一种类型 TBaseList<TBaseObject> - Bimmer_R
显示剩余6条评论
1个回答

19

Delphi泛型不支持协变性和逆变性,因此您所尝试的操作在语言的当前语法下是不可能的。我建议您阅读以下涵盖此问题更详细的博客文章:

从根本上说,您正在尝试执行以下操作:

type
  TBase = class;
  TDerived = class(TBase);
  TBaseList = TList<TBase>;
  TDerivedList = TList<TDerived>;
var
  BaseList: TBaseList;
  DerivedList: TDerivedList;
...
BaseList := TDerivedList;//does not compile

设计师们并非出于恶意阻止您这样做。原因很简单。请考虑以下标准示例:

type
  TAnimal = class;
  TCat = class(TAnimal);
  TPenguin = class(TAnimal);

var
  AnimalList: TList<TAnimal>;
  CatList: TList<TCat>;
  Penguin: TPenguin;
...
AnimalList := CatList;//does not compile because...
AnimalList.Add(Penguin);//...of the danger of this

尽管在 TAnimal 类型的 TList<TAnimal> 中添加一个 TPenguin 是合理的,但实际上 AnimalList 引用的列表是 TList<TCat>,而企鹅不是猫。

如果您想在示例层次结构中思考它,这里是一段代码说明语言设计的合理性。

aList := aCustomerList;//does not compile
aList.Add(aCustomPerson);
//this would add a TCustomPerson instance to a list containing 
//TCustomer instances, but a TCustomPerson is not a TCustomer

感谢您为我的直觉提供了措辞。我“知道”它一定是这样的,但无法想出措辞或示例。 - Marjan Venema
5
读完您提供的链接 - 我必须说...谢谢。这些信息应该包含在泛型帮助文件中 - 尤其是TObjectList! - Bimmer_R
你能否提供更多关于为什么不支持这个功能的细节?第一个链接已经不存在了,即使在存档中也找不到它,我担心第二个链接也会出现同样的情况,而那里面有很好的解释。 - Triber
@DavidHeffernan 谢谢,我之前尝试过 web.archive。 - Triber

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