【问题标题】:Cannot pass a List<Foo> to a method expecting a List<IFoo>, where Foo : IFoo无法将 List<Foo> 传递给需要 List<IFoo> 的方法,其中 Foo : IFoo
【发布时间】:2011-08-01 17:34:22
【问题描述】:

我有一个类Foo 实现IFoo 接口。我有一个将List&lt;IFoo&gt; 作为参数的方法。但是,它不能从List&lt;Foo&gt; 转换为List&lt;IFoo&gt;——这让我很吃惊,因为Foo 实现了IFoo 接口。

我该如何解决这个问题,为什么会发生这种情况? (从错误中学习总是好的)

【问题讨论】:

  • 你需要分享我认为的代码。如果您的方法接受 List 并且您的对象 Foo 实现了 IFoo 那么这是完全有效的。
  • 感谢您的课程。我在刷新之前添加了评论。

标签: c# list generics interface casting


【解决方案1】:

这是因为List&lt;T&gt; 不是协变的。详情请见Covariance and Contravariance in C#。

如果您可以让您的方法在 IEnumerable&lt;T&gt; 上运行,这将起作用(您可以在 .NET 4 中将 List&lt;Foo&gt; 传递到 IEnumerable&lt;IFoo&gt;,因为它已定义为 IEnumerable&lt;out T&gt;)。

List&lt;T&gt; 不是协变的原因,顺便说一句,是因为它没有定义只读合约。由于List&lt;T&gt; 明确允许您添加元素(通过Add(T)),因此允许协方差起作用是不安全的。如果允许这样做,该方法期望能够将Bar 类型的元素(如果Bar 派生自IFoo)添加到列表中,但这会失败,因为列表是真的 List&lt;Foo&gt;,而不是 List&lt;IFoo&gt;。由于IEnumerable&lt;T&gt; 只允许您遍历列表,但不能修改它,因此它可以是协变的,并且在这种情况下可以按预期工作。

【讨论】:

  • 这非常有用;谢谢!我用IEnumerable&lt;IFoo&gt; 替换了参数定义(?)并且错误消失了;但是,这样做是否有可能隐藏错误?我宁愿找到一个可靠的解决方案,而不仅仅是隐藏错误..(我只是迭代列表并调用方法Bar)
  • @共产鸭:不。这就是为什么IEnumerable&lt;out T&gt; 在定义中有“out”。正如您在这里所做的那样,使用具有协方差的 IEnumerable 是非常安全的。只要您进行迭代,它就可以完美运行(这就是该语言现在支持它的原因!)
【解决方案2】:

信不信由你,这不是类型安全的。

考虑以下代码:

List<Foo> fooList = new List<Foo>();
List<IFoo> iFooList = fooList;    //Illegal

iFooList.Add(new SomeOtherFoo()); //That's not Foo!

您要求进行 协变 转换;这仅适用于不可变类型。
另外,.Net 只支持接口的协方差。

将方法更改为采用IEnumerable&lt;IFoo&gt; 即可。

【讨论】:

    【解决方案3】:

    请记住,泛型类型的特化之间没有继承或实现关系。 List&lt;Foo&gt; 不会以任何方式继承自 List&lt;IFoo&gt;。这就像将string 与int 进行比较。

    这并不是说类型之间完全没有关系;只是有问题的关系不是继承。相反,我们在这两种类型之间存在 专业化 关系。 .Net 4.0 及更高版本中的某些类型支持称为协方差 的专业化属性。 Co- and contra- variance 在某些情况下允许泛型类型的特殊部分变化(注意那里的词根)。 List&lt;T&gt; 不是这些类型之一。

    但是在这种情况下(我不确定您是否拥有 .Net 4.0)我会做的是使整个方法通用:

    public void MyMethod<T>(IEnumerable<T> items) where T : IFoo
    {
        //...
    }
    

    请注意,我还使用了IEnumerable&lt;T&gt; 而不是List&lt;T&gt;。您应该仍然可以将列表传递给此函数,因为IEnumerable&lt;T&gt; 和List&lt;T&gt; 之间存在继承/实现关系。您还可以处理数组和任何其他受支持的序列。 IEnumerable 也是支持协方差的类型之一。几乎所有需要List&lt;T&gt; 参数的方法都应该更新为使用IEnumerable&lt;T&gt;。

    【讨论】:

    • 如果您使用的是 .NET 4,则不需要是通用的。由于IEnumerable&lt;out T&gt; 已经是协变接口,因此只需使用public void MyMethod(IEnumerable&lt;IFoo&gt; items) 即可。不过,这是 .NET 2-3.5 中的一个选项。
    【解决方案4】:

    您需要创建一个新的List&lt;IFoo&gt; 并将第一个列表中的所有项目添加到其中。

    【讨论】:

      【解决方案5】:

      好吧,您可以将其转换为声明为 List&lt;IFoo&gt; 的新列表,这正是您所需要的:

      List<Foo> list = new List<Foo>();
      
      theMethodThatTakesIFooList(list.ToList<IFoo>());
      

      我想这会起作用...因为 Foo 可以转换为 IFoo,并且 ToList 返回指定类型的新列表。

      【讨论】:

        猜你喜欢
        • 2019-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-12-12
        • 2012-01-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多