问题
Given the following types:
public interface IPrimary{ void doBattle(); }
// an ISecondary "is" an IPrimary
public interface ISecondary : IPrimary { }
// An implementation of ISecondary is also an IPrimary:
internal class SecondaryImpl : ISecondary
{
// Required, since this is an IPrimary
public void doBattle(){ }
}
Why can I not do this?
List<IPrimary> list = new List<ISecondary>();
This results in the following compilation error:
Argument type 'System.Collections.Generic.List' is not assignable to parameter type 'System.Collections.Generic.List'
I understand the error, and I realize there are workarounds. I just do not see any clear reason why this direct conversion is disallowed. The values contained in a list of ISecondary, should after all, be (by extension) values of type of IPrimary.Why then are List<IPrimary> and List<ISecondary> being interpreted as unrelated types?
Can anyone explain clearly the reasoning for C# being designed this way?
A slightly extended example: I came across the issue when trying to do something similar to the following:
internal class Program
{
private static void Main(string[] args)
{
// Instance of ISecondary, and by extention, IPrimary:
var mySecondaryInstance = new SecondaryImpl();
// This works as expected:
AcceptImpl(mySecondaryInstance);
// List of instances of ISecondary, which are also,
// by extention, instances of IPrimary:
var myListOfSecondaries = new List<ISecondary> {mySecondaryInstance};
// This, however, does not work (results in a compilation error):
AcceptList(myListOfSecondaries);
}
// Note: IPrimary parameter:
public static void AcceptImpl(IPrimary instance){ }
// Note: List of type IPrimary:
public static void AcceptList(List<IPrimary> list){ }
}
回答1:
Why can I not do this?
List<IPrimary> list = new List<ISecondary>();
Imagine that you had a method defined like this:
public void PopulateList(List<IPrimary> listToPopulate)
{
listToPopulate.Add(new Primary()); // Primary does not implement ISecondary!
}
What would happen if you were to pass it a List<ISecondary> as a parameter?
The error that List<ISecondary> is not assignable from List<IPrimary> is the compiler's way of getting you out of such troubles.
回答2:
public class Animal
{
...
}
public class Cat: Animal
{
public void Meow(){...}
}
List<Cat> cats = new List<Cat>();
cats.Add(new Cat());
cats[0].Meow(); // Fine.
List<Animal> animals = cats; // Pretend this compiles.
animals.Add(new Animal()); // Also adds an Animal to the cats list, since animals references cats.
cats[1].Meow(); // cats[1] is an Animal, so this explodes!
And that's why.
回答3:
class Evil : IPrimary {...}
list.Add(new Evil()); // valid c#, but wouldn't work
It is protecting you from an error. The list instance (the object) demands secondary instances. Not every primary is a secondary. Yet the expectation is that a list-of-primary can hold any primary. If we could treat a list-of-secondary as a list-of-primary: bad things.
Actually, arrays do allow this - and error at runtime if you get it wrong.
回答4:
The reason that list types are not covariant in their generic parameter ie that List<ISecondary> is not a subtype of List<IPrimary> is that they are read-write. In your extended example your method AcceptList could do list.Add(x) where x is an IPrimary but not an ISecondary.
Note that IEnumerable<T> is correctly covariant, while arrays are typed covariantly (you can do what you try to above) but for the same reason this is not sound - adding an element to the collection will fail at runtime.
来源:https://stackoverflow.com/questions/14621564/why-cant-a-list-of-an-interface-type-accept-instances-of-an-inheriting-interfac