【问题标题】:Why can I not return a List<Foo> if asked for a List<IFoo>? [duplicate]如果要求 List<IFoo>,为什么我不能返回 List<Foo>? [复制]
【发布时间】:2008-11-25 13:36:22
【问题描述】:

我了解,如果 ST 的子类,那么 List&lt;S&gt; 不是 List&lt;T&gt; 的子类。美好的。但是接口有不同的范式:如果Foo 实现了IFoo,那么为什么List&lt;Foo&gt; 不是List&lt;IFoo&gt;(的一个例子)?

由于没有实际的类IFoo,这是否意味着在公开List&lt;IFoo&gt; 时我总是必须转换列表的每个元素?或者这只是糟糕的设计,我必须定义自己的集合类ListOfIFoos 才能使用它们?在我看来两者都不合理......

鉴于我正在尝试对接口进行编程,公开此类列表的最佳方式是什么?我目前倾向于在内部将我的List&lt;Foo&gt; 实际存储为List&lt;IFoo&gt;

【问题讨论】:

标签: c# generics interface


【解决方案1】:

如果List&lt;IFoo&gt;,您的List&lt;Foo&gt; 不是子类,因为您不能在其中存储MyOwnFoo 对象,而这也恰好是IFoo 实现。 (Liskov substitution principle)

存储List&lt;IFoo&gt; 而不是专用的List&lt;Foo&gt; 的想法是可以的。如果您需要将列表的内容转换为它的实现类型,这可能意味着您的接口不合适。

【讨论】:

    【解决方案2】:

    以下是您为什么不能这样做的示例:

    // Suppose we could do this...
    public List<IDisposable> GetDisposables()
    {
        return new List<MemoryStream>();
    }
    
    // Then we could do this
    List<IDisposable> disposables = GetDisposables();
    disposables.Add(new Form());
    

    此时,为保存 MemoryStreams 而创建的列表现在包含一个表单。不好!

    所以基本上,存在这个限制是为了维护类型安全。在 C# 4 和 .NET 4.0 中,会有 limited 对此的支持(它被称为 variance),但它仍然不支持这种特定场景,原因正是上面给出的.

    【讨论】:

    • 我真的不明白这个。创建该列表是为了保存 IDisposables,而不是 MemoryStreams。你可以用 List 做同样的事情,编译器允许你这样做。接口有什么不同?
    • 不,你不能用 List 做到这一点。尝试将 List 作为 List 返回 - 你会看到它失败。
    • 我真希望我能保存我今天下午写的代码,因为你是对的,它不编译,也不应该编译。我想知道我在想什么。
    【解决方案3】:

    在您的返回函数中,您必须使列表成为接口列表,并且在创建对象时,将其作为实现它的对象。像这样:

    function List<IFoo> getList()
    {
      List<IFoo> r = new List<IFoo>();
      for(int i=0;i<100;i++)
          r.Add(new Foo(i+15));
    
      return r;
    }
    

    【讨论】:

      【解决方案4】:

      大规模编辑 你可以用 C# 4.0 来做,但是 [感谢 Jon]

      您可以使用ConvertAll 解决它:

      public List<IFoo> IFoos()
      {
          var x = new List<Foo>(); //Foo implements IFoo
          /* .. */
          return x.ConvertAll<IFoo>(f => f); //thanks Marc
      }
      

      【讨论】:

      • 除非我误解了,否则你不会。一个列表既不能是协变的,也不能是反变的,因为它允许“in”和“out”的使用。
      • 您还需要限定目标类型:return x.ConvertAll(f=>f);
      • System.Linq 中有一个扩展方法,所以你可以做 return x.Cast();而不是 return x.ConvertAll(f => f);
      【解决方案5】:

      简单的答案是List&lt;Foo&gt;List&lt;IFoo&gt; 不同,就像DateTimeIPAddress 不同一样。

      但是,您拥有IFoo 的事实意味着IFoo 的集合预计至少包含IFoo 的两个实现(FooAFooB 等...),因为如果您期望IFooFoo 的实现只有一种,那么IFoo 类型是多余的。

      因此,如果接口只有一种派生类型,请忘记接口并节省开销。如果一个接口有两个或多个派生类型,则始终在集合/泛型参数中使用该接口类型。

      如果您发现自己在编写 thunking 代码,则可能存在设计缺陷。

      【讨论】:

      • 除了我打算使用不同的实现之外,还有其他使用接口的原因。
      【解决方案6】:

      如果在发明 IList&lt;T&gt; 时,Microsft 已经意识到 .net 的未来版本将支持接口协变和逆变,那么将接口拆分为 IReadableList&lt;out T&gt;、@987654323 将是可能且有用的@ 和 IList&lt;T&gt; 将继承上述两者。这样做会给 vb.net 实现者带来少量额外工作(他们必须同时定义索引属性的只读和读写版本,因为出于某种原因 .net 不允许读写属性作为只读属性),但这意味着只需要从列表中读取项目的方法可以以协变方式接收IReadableList&lt;T&gt;,而只需要它们可以附加到的集合的方法可以接收@ 987654326@ 以逆变方式。

      不幸的是,今天实现这种事情的唯一方法是,如果 Microsoft 提供一种新接口替代旧接口的方法,旧接口的实现会自动使用新接口提供的默认方法。我认为这样的功能(界面可替代性)会非常有帮助,但我不会屏住呼吸等待微软实现它。

      鉴于无法将IReadableList&lt;T&gt; 反向拟合到IList&lt;T&gt;,另一种方法是定义自己的列表相关接口。这样做的一个困难是System.Collections.Generic.List&lt;T&gt; 的所有实例都必须替换为其他类型,尽管如果要在包含单个System.Collections.Generic.List&lt;T&gt; 字段并定义了与系统类型之间的扩展转换(使用结构而不是类意味着代码将避免在任何不需要结构的情况下进行转换时创建新堆对象的需要盒装)。

      【讨论】:

      • 我会非常谨慎地重新定义像 List 这样基本的东西;我怀疑,有人感到困惑的可能性约为 100%。
      猜你喜欢
      • 1970-01-01
      • 2019-01-01
      • 1970-01-01
      • 2012-01-25
      • 1970-01-01
      • 2014-04-04
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多