15

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?

abatishchev
  • 98,240
  • 88
  • 296
  • 433
Raj Rao
  • 8,872
  • 12
  • 69
  • 83

4 Answers4

24

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 IEnumerable<int> into IEnumerable<object> - the boxing conversion doesn't preserve representation).

Glorfindel
  • 21,988
  • 13
  • 81
  • 109
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • @Arnis: Thank you - I enjoyed it myself, even if I didn't get to show the Hokey Cokey video I'd recorded... – Jon Skeet Oct 27 '10 at 14:52
  • 2
    Jon, so did the BCL team add out (IEnumerable : 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 because it is mutable (making it possible to add a different implementation to the underlying list?) – Raj Rao Oct 27 '10 at 17:46
  • 5
    @Rajah: That is pretty much correct. Notice how on IEnumerable, 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 Oct 27 '10 at 20:04
  • 1
    @Neme: Done. (Assuming you meant the link to the video.) – Jon Skeet Jan 03 '18 at 06:42
  • see also: C#'s great big mistake with arrays. – Cory Nelson Jan 03 '18 at 06:44
5

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
  • 59,039
  • 12
  • 119
  • 163
  • 3
    More specifically, it means that `IEnumerable` is covariant *for `T`*. An interface may be variant in some type parameters but not others. – Jon Skeet Oct 27 '10 at 14:49
3

No.

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

SLaks
  • 868,454
  • 176
  • 1,908
  • 1,964
1

IList<T> interface is not covariant.

Andrew Bezzub
  • 15,744
  • 7
  • 51
  • 73