Since some version of this question keeps showing up on StackOverflow, and the answer’s always basically the same, I figured I may as well write up a post on here that people can link to. Here’s the question, in simplified form:
“Why can’t I pass a TList<TMyDerivedObject> to a function that’s expecting a TList<TMyBaseObject>? You can pass a TMyDerivedObject to a parameter expecting TMyBaseObject, so why doesn’t it work for lists?”
It really looks like it ought to be that simple, but unfortunately it’s not. Trying to do this gets into a tricky issue in type theory known as covariance and contravariance, which is a formal way of describing the relationship between different types when one inherits from another.
It’s important to remember that object types don’t really exist at the binary level. They’re an abstraction that makes it easier for us to work with them, but when you get down to it, all that’s there is a sequence of bytes. As a strongly-typed language, it’s up to the Delphi compiler to make sure that, when you have a TMyDerivedObject, it doesn’t get replaced with a TMyIncompatibleObject that will cause things to crash or corrupt your data when you try to use it. This is why you’re not allowed to pass descendant classes to a var parameter, for example: because in the function using the var parameter, you’re allowed to replace the object with something else, and if the compiler can’t guarantee that the replacement will be of a compatible type, it prevents you from passing it in order to preserve type safety.
This is basically the same problem you get when dealing with generic lists. Let’s say you have a TList<TMyDerivedObject> and you pass it to a function that expects a TList<TMyBaseObject>. As long as all it does is reads the items in the list, you’re just fine. But if this function calls .Add on the list and adds a TMyIncompatibleObject, (which also descends from TMyBaseObject, and so is perfectly legal in this context,) then you’ve violated type safety. You have a list that’s supposed to only contain one type of object, and now there’s something incompatible inside. And since this is done by calling a method on the list object and not by directly assigning one variable to another, the compiler can’t check to make sure you’re not doing something dangerous like this, so it forbids the entire concept in order to preserve type safety.
There’s a solution to this problem. You can extend the language syntax a little so that the compiler does have a way to check this. You have to mark the methods on the list so that the compiler knows that it’s safe for them to receive an object that descends from, or is an ancestor of, the specified generic type. For example, you could mark TList<T>.Add with a hint that it’s OK to add descendants of the T type, but not ancestors. Then the compiler uses this to determine more relaxed type safety rules that allow you to pass around different generic types without the danger of mixing incompatible types together.
The latest version of Prism supports this, but the Delphi team hasn’t implemented it yet. I haven’t heard anything to indicate that they’re working on it for Delphi 2011 either. Hopefully it they’ll find some way to implement it for Delphi 2012.