Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

DELPHI: Generics and polymorphism

This has been asked several different ways already - but I haven't found my answer yet.

Can someone clarify a few things for me please. Using : Delphi XE2

I have quite a big BaseObject that I use for almost everything. Along with it I have a Generic list - BaseList.

Delarations go like this :

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

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

I have recently tried to change the TBaseList declaration from a very old TStringList using Objects[] property... to this never more versatile Generics list TObjectList.

But I run into some problems. The BaseUnit is one file ... and every time I descend my BaseObject I also make a specialized list to follow it.

So I would go and do something like :

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

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

But now I would like an object to contain a list - that can hold any object. And I thought I could do it like this

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

Since BaseList and BaseObject is top of my hierarchy I assumed that such a List would be able to hold any list I could think of.

But I have a feeling that it is here I fail ... a TBaseList<TBaseobject> is somehow not comparable to TCustomerList<TCustomer> ... Even if TCustomerList and TCustomer is descended from my base.

I was hoping to be able to use generics in the baselist for instaciating new objects. ie. using T.Create in a populate method.

Here is example of complete hierarchy:

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;

Given the above hierarchy - why can't I :

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;

Calling a procedure that handles the base class for all lists fails the same way ...

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

Can someone punch me and tell me what I'm doing wrong here?

like image 885
Bimmer_R Avatar asked Feb 04 '12 11:02

Bimmer_R


1 Answers

Delphi generics do not support covariance and contravariance so what you are attempting to do is not possible with the language's current syntax. I suggest you have a read of the following blog articles that cover the matter in more detail.

  • Craig Stuntz: Comparing C#, C++, and Delphi (Win32) Generics
  • Mason Wheeler: Generics and the Covariance Problem

Fundamentally what you are attempting to do is this:

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

The designers have not stopped you doing this out of spite. There is a good reason. Consider the following standard example:

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

Whilst it is reasonable to add a TPenguin to a TList<TAnimal>, the actual list that AnimalList refers to is a TList<TCat> and a penguin is not a cat.

And, if you want to think of it in the context of your example hierarchy, here's an illustration of code that justifies the language design.

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
like image 157
David Heffernan Avatar answered Nov 10 '22 05:11

David Heffernan