views:

94

answers:

4

In the code below:

interface I1 { }
class CI1: I1 { }

List<CI1> listOfCI1 = new List<CI1>();

IEnumerable<I1> enumerableOfI1 = listOfCI1; //this works

IList<I1> listofI1 = listOfCI1; //this does not

I am able to assign my "listOfCI1" to an IEnumerable<I1> (due to covariance)

But why am I not able to assign it to an IList<I1>? For that matter, I cannot even do the following:

List<I1> listOfI12 = listOfCI1;

Shouldn't covariance allow me to assign a derived type to a base type?

+1  A: 

No.

Otherwise, you would then be able add a different implementation of I1 to a list that should only contain C1s.

SLaks
+9  A: 

Simply put, IList<T> is not covariant, whereas IEnumerable<T> is. Here's why...

Suppose IList<T> was covariant. The code below is clearly not type-safe... but where would you want the error to be?

IList<Apple> apples = new List<Apple>();
IList<Fruit> fruitBasket = apples;
fruitBasket.Add(new Banana()); // Aargh! Added a Banana to a bunch of Apples!
Apple apple = apples[0]; // This should be okay, but wouldn't be

For lots of detail on variance, see Eric Lippert's blog post series on it, or watch the video of my talk about variance from NDC.

Basically, variance is only ever allowed where it's guaranteed to be safe (and in a representation-preserving way, which is why you can't convert IEnmerable<int> into IEnumerable<object> - the boxing conversion doesn't preserve representation).

Jon Skeet
Btw, that talk was great.
Arnis L.
@Arnis: Thank you - I enjoyed it myself, even if I didn't get to show the Hokey Cokey video I'd recorded...
Jon Skeet
Jon, so did the BCL team add out (IEnumerable<out T> : IEnumerable), because IEnumerable does not allow you to add any new members (ie its immutable) making it safe. But on the other hand did not add "out" on IList<T> because it is mutable (making it possible to add a different implementation to the underlying list?)
Rajah
@Rajah: That is pretty much correct. Notice how on IEnumerable<T>, every "T" appears in an "output" position, never in an "input" position. Hence "out" for covariance and "in" for contravariance. That's how the compiler and the CLR know that it is safe to be variant. (The actual rules are quite a bit more complicated, but that's a reasonable simplification.)
Eric Lippert
+1  A: 

IList<T> interface is not covariant.

Andrew Bezzub
+2  A: 

Compare declarations (msdn)

public interface IEnumerable<out T> : IEnumerable

public interface IList<T> : ICollection<T>, IEnumerable<T>, IEnumerable

you see that magical word out? This means that covariance is on.

Andrey
More specifically, it means that `IEnumerable<out T>` is covariant *for `T`*. An interface may be variant in some type parameters but not others.
Jon Skeet