3

How does this code work without an explicit cast?

static void Main()
{
    IList<Dog> dogs = new List<Dog>();
    IEnumerable<Animal> animals = dogs;
}

I am talking about this line:

IEnumerable<animal> animals = dogs;

You can see here that I am able to pass the variable dogs without an explicit cast. But how is this possible? Why did the compiler allow me do this? Shouldn't I first do the cast like this:

IEnumerable<Animal> animals = (List<Dog>) dogs;

The code will work with the explicit cast and without the explicit cast but I cannot understand why it allowed me to assign dogs to the animals reference variable without an explicit cast.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
BoSsYyY
  • 563
  • 5
  • 13
  • 2
    See [this](https://stackoverflow.com/questions/6732299/why-was-ienumerablet-made-covariant-in-c-sharp-4) answer about IEnumerable and covariance. – Magnus Apr 01 '18 at 11:17
  • Even if introduces in C# 4 it is still one of the most asked questions about C# on stackoverflow today. – Magnus Apr 01 '18 at 11:33
  • 1
    IEnumerable is a very simple interface, there is nothing you can do with it to corrupt the List. No way to add a Cat to that list of dogs or make those dogs say Meow. So the conversion is safe, all you can do is obtain dogs from the list and making them look like an Animal is okay because it is the base class of Dog. It is one of the few interfaces built into the framework that allows this. – Hans Passant Apr 01 '18 at 13:42

4 Answers4

8

IEnumerable<T> is a covariant interface. That means there's an implicit identity conversion from IEnumerable<T1> to IEnumerable<T2> so long as there is an implicit reference conversion or identity conversion from T1 to T2. See the documentation on conversions for more details on this terminology.

That's most often the case if T2 is a base class (direct or indirect) of T1, or is an interface that T1 implements. It's also the case if T1 and T2 are the same type, or if T2 is dynamic.

In your case, you're using an IList<Dog>, and IList<T> implements IEnumerable<T>. That means any IList<Dog> is also an IEnumerable<Dog>, so there's a implicit reference conversion to IEnumerable<Animal>.

A few important things to note:

  • Only interfaces and delegate types can be covariant or contravariant. For example, List<T> isn't covariant and couldn't be. So for example, you couldn't write:

    // Invalid
    List<Animal> animals = new List<Dog>();
    
  • Not all interfaces are covariant or convariant. For example, IList<T> isn't covariant, so this isn't valid either:

    // Invalid
    IList<Animal> animals = new List<Dog>();
    
  • Variance doesn't work with value types, so this isn't valid:

    // Invalid
    IEnumerable<object> objects = new List<int>();
    

Generic variance is only permitted where it's safe:

  • Covariance relies on the type parameter only be present in an "out" position in any signature, i.e. values can come out of the implementation, but are never accepted.
  • Contravariance relies on the type parameter only being present in an "input" position in any signature, i.e. values can be passed into the implementation, but are never returned

It gets a bit confusing when the signatures accept a parameter which is itself contravariant - even though a parameter is normally an "input" position, it's sort of reversed by contravariance. For example:

public interface Covariant<out T>
{
    // This is valid, because T is in an output position here as
    // Action<T> is contravariant in T
    void Method(Action<T> input);
}

Definitely read the linked documentation for more information though!

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 1
    This is a good answer but your initial description of the rules slightly understates the case. For example, `IEnumerable` --> `IEnumerable` even though `dynamic` is not a base class of `Dog`. Or, `IEnumerable>` --> `IEnumerable>` even though `Action` is not a base case of `Action` – Eric Lippert Apr 01 '18 at 11:59
  • 1
    You can make your explanation simpler and more accurate by saying that the rule is that there must be an *implicit reference conversion or identity conversion* from T1 to T2, where as you note, T1 and T2 must be reference types. – Eric Lippert Apr 01 '18 at 12:01
  • @EricLippert: Thanks, fixed. I've kept the bit about base classes and implemented interfaces as a common example, as otherwise "implicit reference conversion" might just cause another level of confusion. I'll try to find docs about that, or refer directly to the specification if necessary. – Jon Skeet Apr 01 '18 at 12:15
  • Okay I've read about covariance in the Microsoft Docs. I've played around with it, it's confusing but I understood the general principle. It's basically the same as delegate's covariance and contravariance but with Generic interfaces as addition to it. Will I ever need to use this concept with Generic interfaces anywhere else besides in working with collections? – BoSsYyY Apr 01 '18 at 13:19
  • @BoSsYyY: I can't really predict whether you ever will. I'd say it's by far the most *common* use of interface variance, but I'm sure there are other interfaces that do use it - whether in the framework, third party libraries you might use, or other code within your app. – Jon Skeet Apr 01 '18 at 13:21
  • Also I think I now have the answer to my question: It works without the cast because IList implements IEnumerable and anything that implements ILIst indirectly implements IEnumerable. So when I assign that animals list to the IEnumerable reference variable the compiler can see that the type of the reference variable is IList and it knows that everything that implements IList also implements IEnumerable and this is the reason why it doesn't require that I cast it. Did I understand it corrrectly? – BoSsYyY Apr 01 '18 at 13:24
  • @BoSsYyY: Yes, that's it. (I've added a little bit more about that in the answer.) – Jon Skeet Apr 01 '18 at 13:34
5

It's because IEnumerable<T> interface is Covariant. More info here: https://learn.microsoft.com/en-us/dotnet/standard/generics/covariance-and-contravariance

MistyK
  • 6,055
  • 2
  • 42
  • 76
0

Because IList< T> inherits from IEnumerable<T>. And I'm guessing your Dog class also inherits from Animal. No cast is needed when assigning to parent classes.

EDIT: And it is possible because the IEnumerable class is covariant.

GregorMohorko
  • 2,739
  • 2
  • 22
  • 33
-1

Although you don't show it, I assume Dog is a subtype of Animal.

You can always assign a subtype to an instance of its parent class.

So List <Dog> can contain all types of dog, thus if you had a class for Poodle, inheriting from Dog, you could put it into the list of dogs.

So an IEumerable<Animal> could contain Dog, Cat and Poodle instances.

In this case, it will happen to contain only Dog or subclasses of Dog.

You could not however do this:

var animals=new List <Animal>();

Ienumerable<Dog> dogs= animals;

Even if you only inserted Dog instances into animals.

Dragonthoughts
  • 2,180
  • 8
  • 25
  • 28