Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

IList<T> and List<T> Conversions with Interfaces

I generally understand interfaces, inheritance and polymorphism, but one thing has me puzzled.

In this example, Cat implements IAnimal and of course List implements IList:

IList<IAnimal> cats = new List<Cat>();

but it generates a compilation error (Cannot implicitly convert type...). It also won't work if I use an asbtract superclass [Animal] that Cat inherits from. However, If I replace IAnimal with Cat:

IList<Cat> cats = new List<Cat>();

it compiles fine.

In my mind, because Cat implements IAnimal, the first example should be acceptable, allowing us to return an interface for both the list and the contained type.

Can anyone explain why it isn't valid? I'm sure there's a logical explanation.

like image 257
Curtmantle Avatar asked Jun 01 '11 16:06

Curtmantle


2 Answers

There is a logical explanation, and this exact question is asked pretty much every day on StackOverflow.

Suppose this was legal:

IList<IAnimal> cats = new List<Cat>(); 

What stops this from being legal?

cats.Add(new Giraffe());

Nothing. "cats" is a list of animals, and a giraffe is an animal, and therefore you can add a giraffe to a list of cats.

Clearly that is not typesafe.

In C# 4 we added a feature whereby you can do that if the metadata annotations allow the compiler to prove that it is typesafe. In C# 4 you can do this:

IEnumerable<IAnimal> cats = new List<Cat>(); 

because IEnumerable<IAnimal> has no Add method, so there is no way to violate type safety.

See my series of articles on how we designed this feature in C# 4 for more details.

http://blogs.msdn.com/b/ericlippert/archive/tags/covariance+and+contravariance/default.aspx

(Start from the bottom.)

like image 118
Eric Lippert Avatar answered Nov 16 '22 01:11

Eric Lippert


C# doesn't support this kind of variance on IList<T> for type-safety reasons.

If C# did support this, what would you expect to happen here?

IList<IAnimal> cats = new List<Cat>();

cats.Add(new Dog());         // a dog is an IAnimal too
cats.Add(new Squirrel());    // and so is a squirrel

In C#4 you're able to do something like this:

IEnumerable<IAnimal> cats = new List<Cat>();

This is because the IEnumerable<T> interface does support variance of this kind. An IEnumerable<T> is a read-only sequence, so there's no way that you could subsequently add a Dog or a Squirrel to an IEnumerable<IAnimal> that's actually a list of Cat.

like image 6
LukeH Avatar answered Nov 16 '22 02:11

LukeH