为啥接口类型列表不能接受继承接口的实例? [复制]
Posted
技术标签:
【中文标题】为啥接口类型列表不能接受继承接口的实例? [复制]【英文标题】:Why can't a list of an interface type accept instances of an inheriting interface? [duplicate]为什么接口类型列表不能接受继承接口的实例? [复制] 【发布时间】:2013-01-15 07:19:44 【问题描述】:给定以下类型:
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)
【问题讨论】:
你想看“C#中的泛型协变” Covariance and contravariance。List<IPrimary> list = new List<ISecondary>();
没有实际意义,即使你可以这样做。
@GrantThomas 好的,可能有点简化了;但是someListOfTypePrimary.AddRange(someProvider.getListOftypeSecondary());
呢?有什么理由不应该发生这种情况吗?
我认为应该允许这样做,因为AddRange
采用IEnumerable<T>
类型,它确实支持协方差-IEnumerable<ISecondary>
可以在任何可以使用IEnumerable<IPrimary>
的地方使用。
【参考方案1】:
class Evil : IPrimary ...
list.Add(new Evil()); // valid c#, but wouldn't work
它可以保护您免受错误的影响。列表实例(对象)需要辅助实例。并非每个主要都是次要的。然而,期望是一个主列表可以保存任何主。如果我们可以将二级列表视为主要列表:坏事。
实际上,数组 确实 允许这样做 - 如果你弄错了,会在运行时出错。
【讨论】:
谢谢,很好的解释。也欣赏有关数组的旁注。【参考方案2】:为什么我不能这样做?
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>
is not assignable from List<IPrimary>
的错误是编译器让你摆脱这些麻烦的方法。
【讨论】:
【参考方案3】:列表类型在它们的泛型参数中不是协变的,即List<ISecondary>
不是List<IPrimary>
的子类型的原因是它们是可读写的。在您的扩展示例中,您的方法AcceptList
可以执行list.Add(x)
,其中x
是IPrimary
,但不是ISecondary
。
请注意,IEnumerable<T>
是正确协变的,而数组是协变类型的(您可以按照上面的尝试进行操作),但出于同样的原因,这并不合理 - 向集合添加元素将在运行时失败。
【讨论】:
【参考方案4】: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!
这就是原因。
【讨论】:
谢谢! +1 是一个有见地和幽默的例子。 :)以上是关于为啥接口类型列表不能接受继承接口的实例? [复制]的主要内容,如果未能解决你的问题,请参考以下文章