DELPHI: Generics and polymorphism
Asked Answered
G

1

13

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?

Giron answered 4/2, 2012 at 11:22 Comment(11)
Can you give some code where you see the failing of this aproach? I'm not sure I've got it.Tedtedd
Are you trying to assign a TCustomerList to FGenList, or are you trying to store TCustomer's in FGenList? (Not that I would then have an answer, but it may clarify what you are trying to achieve). Also, why are you declaring different list types? The point of generics is that you no longer need them for type safety. TList<TCustomer>.Create is type incompatible with TList<TProduct>.Create.Dituri
@Uwe and Marjan : I've edited my question ... grown quite big - I hope it still has context. But perhaps its more understandable now?Giron
yes clearer now. Don't have an answer for you yet, because I am muddling through generics myself and hope someone with more experience with generics will be better able to clarify what is going on.Dituri
Again though why you need the type specific TPersonList and TCustomerList etc? If you need list specific method you could consider aggregation/composition instead of inheritance. Ie a TPersonList = class(TObject) which holds a FList: TBaseList<TBaseObject> member that you instantiate as FList := TBaseList<TPerson>.CreateDituri
OK. I'm old school OOP programmer - generics i also new to me. Why I use inheritance is due to specific methods/properties ... but if I can gain the same with aggregation/composition ... then by all means I would try. Another reason is - I've programmed inheritance the last 20 years - so It's in my bones :-)Giron
LOL: I know the feeling.Dituri
I would love this question if I could see any practical NEED to do covariance, instead of to use some other approach. Otherwise it's just a C#-me-too-ism.Intestate
Well I've got about 200 units built on heritance. And specialized list's is a must for me. So I have to find a way to master a new programming art. And believe me - it gets tougher by the years :-) @WarrenP : have just read your blog - nice by the way - who am I complaining about age :-) ...Giron
@MarjanVenema: The suggestion you proposed a few days ago seems to be a good idea. But trying it out actually reveals the very problem I have. The FList := TBaseList<TPerson>.Create is not possible - since FList is of another Type - TBaseList<TBaseObject>.Giron
hmm. Makes sense. Checking my code it turns out I have a TInterfacedList<T> = class(TInterfacedObject, IList<T>) with a private FList: TList<T>; member and a protected function Add(const Value: T): Integer; The Add function checks that the object instance passed in is actually an object of the required type. See #6389857 for the code (the question was more oriented towards creating an interfaced list and supporting enumerators)Dituri
I
19

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.

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
Illegible answered 4/2, 2012 at 15:38 Comment(5)
Thanks for giving words to my intuition. I "knew" it had to be something like this, but couldn't come up with the words or the examples.Dituri
Having read the links you provided - I must say ... THANKS. This information should be included in the help for generics - TObjectList especially!.Giron
Could you add more detail about why this feature is not supported? The first link no longer exists and I couldn't find it even in the archive and I'm afraid it might happen to the second one too, where it's well explained.Falsehood
@Falsehood google webcache has this: webcache.googleusercontent.com/…Illegible
@DavidHeffernan thanks, i was trying web.archive previouslyFalsehood

© 2022 - 2024 — McMap. All rights reserved.