Приведение TList‹T:class› к TList‹W:class›

У меня есть список типа TList<TForm>. Мне нужно бросить его и использовать как TList<TObject> вот так:

procedure mainForm.testCast;
var
  listT: TList<TForm>;
  listW: TList<TObject>;
  obj: TObject;
begin
  listT := TList<TForm>.create;
  listT.add(form1);
  listT.add(form2);

  listW := TList<TObject>(listT);  // Casting is OK

  // This works, but is this fine?
  for obj in listW do
    memo1.lines.add(obj.className);

end;

Образец работает так, как ожидалось, но можно ли использовать его между общими списками? Приведет ли это к повреждению структуры данных и т. д.? Я использую его только для зацикливания (DoGetEnumerator) и некоторых проверок строк, т.е. я не буду добавлять/удалять элементы.

Реальная функция немного сложнее. Он получает ссылку на listT, используя RTTI в файле TValue. Основная цель не связать FMX.Forms в моём подразделении.

Обновление: Почему TGeneric‹Base› и TGeneric‹ Потомки› несовместимых типов?


person iPath ツ    schedule 10.02.2014    source источник
comment
Это будет работать нормально, но это немного отрывочно. Я сомневаюсь, что вам нужно использовать приведение здесь в любом случае. Вы можете связаться с счетчиком через RTTI.   -  person David Heffernan    schedule 10.02.2014
comment
Поскольку TForm является производным от TObject, а TList<TSomeThing> — это общий список типизированных указателей, в том, что вы делаете, нет ничего плохого. То есть с использованием методов из базового класса.   -  person LU RD    schedule 10.02.2014
comment
@LURD Я не думаю, что это правильно. TList<TObject> не является базовым классом для TList<TForm>. Вы обнаружите, что TList<TForm> is TList<TObject> оценивается как False. Общая база TList<T>. Другой способ думать об этом. Если бы это было просто использование методов из базового класса, зачем понадобилось бы приведение?   -  person David Heffernan    schedule 10.02.2014
comment
@LURD Извините, я ошибся в коде в этом комментарии. Конечно, is здесь не годится, потому что это оба класса. я имел в виду TList<TForm>.InheritsFrom(TList<TObject>)   -  person David Heffernan    schedule 10.02.2014
comment
@DavidHeffernan, под базовым классом я имел в виду в первую очередь TFormvs TObject. Я ожидаю, что TList<TForm> будет вести себя так же, как TList<TObject>, в то время как это не будет верно для любого TList<T>.   -  person LU RD    schedule 10.02.2014
comment
@LURD Код в вопросе будет работать, но только благодаря недокументированным деталям реализации.   -  person David Heffernan    schedule 10.02.2014
comment
@DavidHeffernan, я здесь не с тобой, недокументированная деталь реализации. Почему TList<T> обрабатывает T иначе, если T — это любой объект, основанный на TObject? Технически, я думаю, это было бы возможно, но не слишком ли это далеко?   -  person LU RD    schedule 10.02.2014
comment
@LURD Если бы актерский состав был законным, его можно было бы выполнить с as   -  person David Heffernan    schedule 10.02.2014


Ответы (1)


Что ж, ваш код будет работать, но, на мой взгляд, несколько сомнительно. Проще говоря, приведение не является законным, потому что

TList<TForm>.InheritsFrom(TList<TObject>)

является ложным. Таким образом, объект TList<TForm> не является TList<TObject>. Если бы это было так, то актерский состав был бы не нужен.

Это так, потому что универсальные типы Delphi инвариантны. Более подробную информацию можно найти здесь: Почему класс, реализующий интерфейс, несовместим с типом интерфейса при использовании в дженериках?

Если у вас возникли трудности с пониманием того, почему разработчики сделали универсальные типы инвариантными, подумайте на мгновение о последствиях написания listW.Add(TObject.Create) в вашем коде. Подумайте, что это значит для истинного базового объекта типа TList<TForm>.

Так что язык вам ничего не обещает. Вы выходите за рамки своих гарантий. Так получилось, что реализация этих двух несвязанных типов достаточно совместима для того, чтобы ваш код работал. Но на самом деле это просто случайность реализации.

Поскольку вы уже используете RTTI, я предлагаю вам пройтись по списку с помощью RTTI. Вы можете вызывать GetEnumerator и так далее, используя RTTI. Таким образом, вы будете вызывать фактические методы объекта.

person David Heffernan    schedule 10.02.2014
comment
Спасибо @ Дэвид-Хеффернан! Моя цель — перечислить дочерние элементы Form с помощью RTTI. Перечислитель возвращает TEnumerator‹TFmxObject›, но я не хочу включать FMX.Types в свой модуль. Поэтому я хотел привести результат к чему-то вроде TEnumerator‹TComponent›, что поднимает вопрос выше. - person iPath ツ; 10.02.2014
comment
Вам не нужно не приводить к конкретному типу. Вам нужно полностью погрузиться в мир RTTI. Ваш код будет работать, но для этого он зависит от деталей реализации. - person David Heffernan; 10.02.2014
comment
Вам нужно полностью погрузиться в мир RTTI — хороший момент :) - person iPath ツ; 10.02.2014
comment
кстати, моя простая (и может быть неправильная) логика была такова: если W и T являются экземплярами класса, то вы можете привести их к общему предку, т.е. TObject, если вы уверены, что они являются экземплярами класса. - person iPath ツ; 10.02.2014
comment
W и T имеют общего предка. Но это не то, с чем мы имеем дело. Это TList<W> и TList<T>, и мы находимся в общей инвариантности. Мой ответ на вопрос, на который я ссылался, и ссылки оттуда стоит прочитать. Возможно, я буду более успешен в том, чтобы помочь вам понять общую дисперсию, чем с тем, кто задал этот вопрос! ;-) - person David Heffernan; 10.02.2014