I generally understand interfaces, inheritance and polymorphism, but one thing has me puzzled.
In this example, Cat implements IAnimal
There is a logical explanation, and this exact question is asked pretty much every day on StackOverflow.
Suppose this was legal:
IList<IAnimal> cats = new List<Cat>();
What stops this from being legal?
cats.Add(new Giraffe());
Nothing. "cats" is a list of animals, and a giraffe is an animal, and therefore you can add a giraffe to a list of cats.
Clearly that is not typesafe.
In C# 4 we added a feature whereby you can do that if the metadata annotations allow the compiler to prove that it is typesafe. In C# 4 you can do this:
IEnumerable<IAnimal> cats = new List<Cat>();
because IEnumerable<IAnimal>
has no Add method, so there is no way to violate type safety.
See my series of articles on how we designed this feature in C# 4 for more details.
http://blogs.msdn.com/b/ericlippert/archive/tags/covariance+and+contravariance/default.aspx
(Start from the bottom.)
C# doesn't support this kind of variance on IList<T>
for type-safety reasons.
If C# did support this, what would you expect to happen here?
IList<IAnimal> cats = new List<Cat>();
cats.Add(new Dog()); // a dog is an IAnimal too
cats.Add(new Squirrel()); // and so is a squirrel
In C#4 you're able to do something like this:
IEnumerable<IAnimal> cats = new List<Cat>();
This is because the IEnumerable<T>
interface does support variance of this kind. An IEnumerable<T>
is a read-only sequence, so there's no way that you could subsequently add a Dog
or a Squirrel
to an IEnumerable<IAnimal>
that's actually a list of Cat
.
This type of covariance is not supported in C# 4.0. It is reasonable to expect the behavior you want, it just isn't supported (for now).
You can achieve that using LINQ:
IList<IAnimal> cats = new List<Cat>().Cast<IAnimal>();
If you need covariance and contravariance on a list-like interface, you should define an interfaces IReadableList<out T> and IWritableList<in T>, and derive a type from List<T> which implements both ReadableList<T&, and WriteableList<T>. This would then make it possible to pass a NewList<Cat> to a routine which expects a ReadableList<Animal> or a WritableList<SiameseCat>.
IList<T>
is not a covariant interface (or it would be IList<out T>
). This is because IList both takes type T
as a parameter and returns it as a return value from methods, which makes covariance problematic.
For example, if in your example:
IList<IAnimal> cats = new List<Cat>();
You wanted to add a new cat, from cats, it would allow:
cats.Add(new Dog());
assuming Dog also implemented IAnimal, this is obviously incorrect and wouldn't work. Which is why IList is not a covariant or contravariant interface.