为什么接口类型的列表不能接受继承接口的实例
本文关键字:接口 实例 继承 列表 接口类型 为什么 不能接受 | 更新日期: 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)
其中x
是IPrimary
而不是ISecondary
。
请注意,IEnumerable<T>
是正确的协变,而数组是协变类型的(您可以执行上述操作),但出于同样的原因,这并不合理 - 向集合添加元素将在运行时失败。