【问题标题】: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 contravarianceList&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; 的地方使用。

标签: c# list types interface


【解决方案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 是一个有见地和幽默的例子。 :)
      猜你喜欢
      • 1970-01-01
      • 2014-06-01
      • 2014-09-09
      • 1970-01-01
      • 2013-09-20
      • 2023-03-13
      • 2010-11-18
      • 2019-08-04
      • 1970-01-01
      相关资源
      最近更新 更多