I have a list of type TList<TForm>
. I need to cast it and use it as TList<TObject>
like this:
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;
The sample works as expected, but is it ok to cast like this between generic lists? Will this cause some data structure corruption etc?
I use it only for looping (DoGetEnumerator
) purposes and some string checks i.e. I'll not add/remove items.
The real function is little more complicated. It gets reference to listT
using RTTI in a TValue
.
The main goal is not to link FMX.Forms
in my unit.
Update: Why are TGeneric<Base> and TGeneric<Descendant> incompatible types?
Well, your code will work, but it somewhat dubious in my view. Simply put the cast is not legal because
TList<TForm>.InheritsFrom(TList<TObject>)
is false. So a TList<TForm>
object is not a TList<TObject>
. If it were, then the cast would not be needed.
That this is so is because Delphi's generic types are invariant. More details can be found here: Why is a class implementing an interface not compatible with the interface type when used in generics?
If you have any difficulty understanding why the designers made generic types invariant, consider for a moment the effect of writing listW.Add(TObject.Create)
in your code. Think what it means to the true underlying object of type TList<TForm>
.
So the language promises you nothing. You are venturing outside its guarantees. It so happens that the implementation of these two un-related types is compatible enough for your code to work. But that is really just an accident of implementation.
Since you are already using RTTI, then I suggest that you iterate over the list with RTTI. You can call GetEnumerator
and so on using RTTI. That way you will call the actual methods of the object.