Learner
Learner

Reputation: 990

IList<T> vs IEnumerable<T>: Why is this assignment invalid?

Elephant : Animal....

IList<Elephant> elephants = new List<Elephant>();
IEnumerable<Animal> animalList = elephants;

this works fine but..

IList<Elephant> elephants = new List<Elephant>();
IList<Animal> animalList = elephants;

throws an error. Why is that?

Upvotes: 0

Views: 735

Answers (2)

jason
jason

Reputation: 241701

If this

IList<Elephant> elephants = new List<Elephant>();
IList<Animal> animalList = elephants;

were possible you could then do this

animalList.Add(new Animal());

but animalList is really a reference to the same referrent as elephants. And since elephants is an IList<Elephant> you'd be trying to add an instance of Animal to an collection that can only contain instances of Elephant.

The reason that it is possible for IEnumerable<T> is that IEnumerable<T> is covariant in the type parameter T. This is because T only appears in "out" positions in the interface IEnumerable<T>. Since you're only consuming instances of T from the IEnumerable<T>, it is safe to assign instances of IEnumerable<Derived> to variables of type IEnumerable<Base>. Anything that comes out of IEnumerable<Derived> is a Base and this is why something that implements IEnumerable<Derived> can be used as a IEnumerable<Base> safely. This is why it is safe to assign an instance of IEnumerable<Elephant> to a variable of type IEnumerable<Animal>.

But you get in trouble with IList<T> because IList<T> is not covariant in the type parameter T. The issue here is that T appears in "in" positions in the interface IList<T>. So if you could assign instances of IList<Derived> to variables of type IList<Base>, then you could try to stick instances of Base into the IList<Base> which would really be trying to stick instances of Base into an instance of IList<Derived> and that is clearly not safe to do. This is exactly the issue that we just ran into with IList<Elephant> and IList<Animal>.

Note that IList<T> is also not contravariant in the type parameter T. This is because T appears in "out" positions in the interface IList<T>. If you could assign instances of IList<Base> to a variable of type IList<Derived> then you could try to grab an instance of Derived out of the IList<Derived> which would be trying to grab an instance Derived out of an instance of IList<Base> and that is clearly absurd.

Upvotes: 11

Xavier Poinas
Xavier Poinas

Reputation: 19743

This is called covariance and contravariance.

I won't go into details but you can read about it.

Upvotes: 1

Related Questions