DELPHI: Дженерики и полиморфизм - PullRequest
9 голосов
/ 04 февраля 2012

Об этом уже спрашивали несколько разных способов - но я еще не нашел свой ответ.

Может кто-нибудь прояснить несколько вещей для меня, пожалуйста. Использование: Delphi XE2

У меня довольно большой BaseObject, который я использую практически для всего. Наряду с этим у меня есть общий список - BaseList.

Отставания идут так:

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

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

Недавно я пытался изменить объявление TBaseList с очень старого 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 сошли с моей базы.

Я надеялся, что смогу использовать дженерики в базовом списке для создания новых объектов. то есть. используя 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;

Может кто-нибудь ударить меня и сказать, что я здесь не так делаю?

1 Ответ

16 голосов
/ 04 февраля 2012

Обобщения 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

Хотя разумно добавить TPenguin к TList<TAnimal>, фактический список, на который ссылается 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
...