0

This is probably a duplicate. But after a day of research I was hoping for your leniency. It is a question about covariance.

Why can I do this?

List<Animal> allAnimals = new List<Animal>();
allAnimals.Add(new Dog());
allAnimals.Add(new Cat());

But cannot do this?

List<Dog> aBunchOfDogs = new List<Dog>();
allAnimals = aBunchOfDogs;

I have read a few discussions on covariance.

"Covariance in C#"

"Converting lists to interface lists"

"Casting list to interface list"

My understanding is, that I cannot add a list of dogs to a list of animals because then I could add a cat to the animal list, which would be dangerous. But adding a single instance of a cat or a dog to a list of animals is just as dangerous isn't it? It seems to me that C# is undecided on whether to trust me or not.

  • 1
    `allAnimals = aBunchOfDogs`: now `allAnimals` is a reference to the same underlying list that `aBunchOfDogs` refers to, which is a `List`. Now do `allAnimals.Add(new Cat())` -- oops! We added a `Cat` to our `List`. `aBunchOfAnimals[^1]` and `aBunchOfDogs[^1]` now both return a `Cat`. That's not right! – canton7 Oct 22 '21 at 08:17
  • Can't you do something like `allAnimals = aBunchOfDogs as List;`? – Rafalon Oct 22 '21 at 08:17
  • 1
    @Rafalon You can try, but `allAnimals` will be `null`... – canton7 Oct 22 '21 at 08:17
  • 1
    @canton7 just tried in .net fiddle, and it results in compilation error, so we can't even try it. But we can do `allAnimals = new List(aBunchOfDogs);` – Rafalon Oct 22 '21 at 08:22
  • Because List is not a List , so it's difference , not about dangerous . – TimChang Oct 22 '21 at 08:27
  • @Rafalon You can, and that's safe because it creates a *new* list -- there's no way for someone to insert a `Cat` into `allAnimals`, and for it to pop up in the `aBunchOfDogs` list – canton7 Oct 22 '21 at 08:27
  • canton's comment above made me re-read the question: "*because then I could add a cat to the animal list*" - no, it's not because of that. It is because then you could add a cat to the **dog** list (by adding it to the animal list), and **that** is not possible. "*I cannot add a list of dogs to a list of animals*" - this is wrong too: you **can** add a list of dogs to a list of animals (`allAnimals = new List(); allAnimals.AddRange(aBunchOfDogs);` should work fine). What you can't do is using the same *reference* and say somewhere it is a `List` and elsewhere it's a `List` – Rafalon Oct 22 '21 at 08:40
  • allAnimals = aBunchOfDogs <-- this line mean you want to cast aBunchOfDogs to a allAnimals , is not add all dogs element to allAnimals. It's diffrence things. if you want to add all dogs elements , you need add dog element one by one. – TimChang Oct 22 '21 at 08:47
  • 2
    Man. I think I got it all wrong. Thanks for all your help. My brain is still trying to figure it out but your comments will make that happen. It makes sense. I can add a cat or a dog or a bunch of dogs to a list of animals, because I will only ever be able to use the animal list as animals. But I can't have a list of animals be a reference to a list of dogs. Because then I could break the dog list by adding a cat. Thanks a lot! – Capricornum Oct 22 '21 at 09:39

1 Answers1

5

Assuming you understand why you can pass a Dog and Cat instance to a method accepting Animal, here's why your latter example isn't allowed:

List<Dog> aBunchOfDogs = new List<Dog>();
allAnimals = aBunchOfDogs;
allAnimals.Add(new Cat()); // Fine because allAnimals is List<Animal>

foreach (var dog in aBunchOfDogs)
{
    dog.Woof(); // The Cat can't woof
}
Johnathan Barclay
  • 18,599
  • 1
  • 22
  • 35
  • Great example! Easy to understand! – Wolfgang Amadeus Oct 22 '21 at 08:27
  • Thank you Johnathan. It really hard for me to wrap my head around but it's beginning to make sense. I think unconsciously I believed "allAnimals = aBunchOfDogs" to mean "allAnimals = new List(aBunchOfDogs)", which of course is completely wrong. – Capricornum Oct 22 '21 at 09:42
  • @Capricornum Yeah, the instance (runtime) type here will always be `List`; if you were able to assign it to a `List` variable that would only be checked at compile time; the runtime type never changes. – Johnathan Barclay Oct 22 '21 at 10:59