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 t开发者_C百科here's a logical explanation.
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.)
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
.
You can achieve that using LINQ:
IList<IAnimal> cats = new List<Cat>().Cast<IAnimal>();
IList<T>
is not a covariant interface (or it would be IList<out T>
). This is because IList both takes type T
as a parameter and returns it as a return value from methods, which makes covariance problematic.
For example, if in your example:
IList<IAnimal> cats = new List<Cat>();
You wanted to add a new cat, from cats, it would allow:
cats.Add(new Dog());
assuming Dog also implemented IAnimal, this is obviously incorrect and wouldn't work. Which is why IList is not a covariant or contravariant interface.
This type of covariance is not supported in C# 4.0. It is reasonable to expect the behavior you want, it just isn't supported (for now).
If you need covariance and contravariance on a list-like interface, you should define an interfaces IReadableList<out T> and IWritableList<in T>, and derive a type from List<T> which implements both ReadableList<T&, and WriteableList<T>. This would then make it possible to pass a NewList<Cat> to a routine which expects a ReadableList<Animal> or a WritableList<SiameseCat>.
精彩评论