为啥接口类型列表不能接受继承接口的实例? [复制]

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&lt;IPrimary&gt;List&lt;ISecondary&gt; 被解释为不相关的类型?

谁能解释清楚 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&lt;IPrimary&gt; list = new List&lt;ISecondary&gt;(); 没有实际意义,即使你可以这样做。 @GrantThomas 好的,可能有点简化了;但是someListOfTypePrimary.AddRange(someProvider.getListOftypeSecondary()); 呢?有什么理由不应该发生这种情况吗? 我认为应该允许这样做,因为AddRange 采用IEnumerable&lt;T&gt; 类型,它确实支持协方差-IEnumerable&lt;ISecondary&gt; 可以在任何可以使用IEnumerable&lt;IPrimary&gt; 的地方使用。 【参考方案1】:
class Evil : IPrimary ...
list.Add(new Evil()); // valid c#, but wouldn't work

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

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

【讨论】:

谢谢,很好的解释。也欣赏有关数组的旁注。【参考方案2】:

为什么我不能这样做? List&lt;IPrimary&gt; list = new List&lt;ISecondary&gt;();

想象一下你有一个这样定义的方法:

public void PopulateList(List<IPrimary> listToPopulate)

    listToPopulate.Add(new Primary());  // Primary does not implement ISecondary!

如果您将List&lt;ISecondary&gt; 作为参数传递给它会发生什么?

List&lt;ISecondary&gt; is not assignable from List&lt;IPrimary&gt;的错误是编译器让你摆脱这些麻烦的方法。

【讨论】:

【参考方案3】:

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

请注意,IEnumerable&lt;T&gt; 是正确协变的,而数组是协变类型的(您可以按照上面的尝试进行操作),但出于同样的原因,这并不合理 - 向集合添加元素将在运行时失败。

【讨论】:

【参考方案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 是一个有见地和幽默的例子。 :)

以上是关于为啥接口类型列表不能接受继承接口的实例? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

█■为啥要用实现接口的类实例化接口呢? ?

接口内部类

为啥静态类不能实现接口? [复制]

获取继承特定接口的类列表? [复制]

C#接口

总结接口与抽象类的异同点