Why can't a list of an interface type accept instances of an inheriting interface? [duplicate]

∥☆過路亽.° 提交于 2019-12-18 04:10:33

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!