为什么接口类型的列表不能接受继承接口的实例

本文关键字:接口 实例 继承 列表 接口类型 为什么 不能接受 | 更新日期: 2023-09-27 17:57:00

给定以下类型:

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(){ }
}

为什么我不能这样做?

List<IPrimary> list = new List<ISecondary>();

这会导致以下编译错误:

参数类型"System.Collections.Generic.List"不能分配给参数类型"System.Collections.Generic.List"

我理解该错误,并且意识到有解决方法。我只是看不出任何明确的理由为什么不允许这种直接转换。ISecondary列表中包含的值毕竟应该是(通过扩展)IPrimary类型的值。那么,为什么List<IPrimary>List<ISecondary>被解释为不相关的类型呢?

谁能清楚地解释以这种方式设计 C# 的原因?

一个稍微扩展的示例:我在尝试执行类似于以下内容的操作时遇到了该问题:

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){  }
}

为什么接口类型的列表不能接受继承接口的实例

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!

这就是原因。

为什么我不能这样做? List<IPrimary> list = new List<ISecondary>();

假设您有一个定义如下的方法:

public void PopulateList(List<IPrimary> listToPopulate)
{
    listToPopulate.Add(new Primary());  // Primary does not implement ISecondary!
}

如果要将其作为参数传递List<ISecondary>,会发生什么情况?

List<ISecondary>无法从List<IPrimary>分配的错误是编译器让您摆脱此类麻烦的方式。

class Evil : IPrimary {...}
list.Add(new Evil()); // valid c#, but wouldn't work

它可以保护您免受错误的影响。列表实例(对象)需要辅助实例。并非每个主要都是次要的。然而,期望是主列表可以容纳任何主列表。如果我们能把次要列表视为主要列表:坏事。

实际上,数组确实允许这样做 - 如果你弄错了,在运行时会出错。

列表类型在其泛型参数中不是协变的,即List<ISecondary>不是List<IPrimary>的子类型的原因是它们是可读写的。在您的扩展示例中,您的方法AcceptList可以执行list.Add(x)其中xIPrimary而不是ISecondary

请注意,IEnumerable<T> 是正确的协变,而数组是协变类型的(您可以执行上述操作),但出于同样的原因,这并不合理 - 向集合添加元素将在运行时失败。