【问题标题】:With C# 3.0, how to write Interface based code with generic collection?使用 C# 3.0,如何使用泛型集合编写基于接口的代码?
【发布时间】:2009-09-04 15:40:18
【问题描述】:

我想编写解耦和干净的代码,并且我知道通过对接口而不是实现进行编程,我的代码将更加灵活和可扩展。所以,不要写这样的方法:

  bool IsProductAvailable(ProductTypeA product);

我写的方法如下:

  bool IsProductAvailable(IProduct product);

只要我的产品实现了 IProduct:

  class ProductTypeA : IProduct

我应该没事的。一切都很好,直到我开始使用泛型集合。由于 C# 3.0 不支持协变和逆变,即使 ProuctTypeA 和 ProductTypeB 都实现了 IProduct,所以不能将 List 放入 List。这很麻烦,因为很多时候我想写这样的东西:

bool AreProductsAvailable(List<IProduct> products);

这样我就可以通过以下方式检查产品的可用性:

List<ProductA> productsArrived = GetDataFromDataabase();
bool result = AreProductsAvailable(productsArrived);

我只想编写一个适用于所有 IProduct 集合的 AreProductsAvailable() 方法。

我知道 C# 4.0 将支持协变和逆变,但我也意识到其他库似乎已经解决了问题。比如我在试用ILOG Gantt这个甘特图控件,发现他们有很多这样的集合接口:

IActivityCollection
ILinkCollection 

所以看起来他们的方法是用接口包装通用集合。所以代替“bool AreProductsAvailable(List products);”,我可以这样做:

bool AreProductsAvailable(IProductCollection products);

然后编写一些代码,以便 IProductCollection 获取 IProduct 的任何通用集合,无论是 List 还是 List。

但是,我不知道如何编写具有“魔力”的 IProductCollection 接口。 :-

有人能给我一些启示吗?这一直困扰着我很长时间,我非常想做“正确的事”。好的,谢谢!

【问题讨论】:

    标签: c# collections generics


    【解决方案1】:

    即使使用 .NET 4.0,List&lt;T&gt; 也不会变体,原因有两个:

    • 它是一个类,而不是接口或委托
    • 它在输入和输出位置都使用T(这就是为什么IList&lt;T&gt; 在.NET 4.0 中也不是变体)

    这个问题的一个常见解决方案是添加另一个类型参数:

    bool AreProductsAvailable<T>(IList<T> products) where T : IProduct
    

    然后您可以遍历列表,将每个元素作为 IProduct 访问 - 但调用者可以使用任何兼容的列表类型。

    【讨论】:

      【解决方案2】:

      AreProductsAvailable(IEnumerable&lt;IProduct&gt; products)怎么样?

      这样称呼它:

      using System.Linq;
      
      List<ProductTypeA> products = ...;
      AreProductsAvailable(products.Cast<IProduct>());
      

      【讨论】:

      • 这绝对是Deecay所追求的。
      【解决方案3】:

      public bool AreProductsAvailable&lt;T&gt;(List&lt;T&gt; products) where T : IProduct { ... }

      【讨论】:

        【解决方案4】:

        我知道这部分不能回答你的问题,但你可能已经过度使用接口了。

        使用接口的经验法则是:

      • 如果 B 是您的类并且它是 A,那么 A 应该是一个接口。
      • 如果 B 是您的类并且它是 A,则 A 应该是基类。

      • 您的设计可能的接口示例:
        可序列化、可交易、可发货

        您的设计可能的类/基类示例:
        产品、客户

        这部分可能会回答您的问题:

        使用 ICollection 或 IEnumerable(或 IProduct/ProductBase)。
        List、ObservableCollection、Dictionary 等集合实现了这些接口,您可以轻松定义实现它们的新集合。

        那么您的代码将是:
        bool AreProductsAvailable(ICollection 产品)
        {
        foreach(产品中的 var 产品)
        if (product.IsAvailable) ...
        }

        只需确保您的所有 GetProducts() 方法都返回基本类型/接口的集合。

        【讨论】:

          【解决方案5】:

          我可能不会以这种方式解决这个问题(尽管我意识到这可能是一个例子,而不是真实的场景......)

          在使用接口时,消费者不应该关心接口背后的具体实现。这种抽象完全是使用接口的重点。

          由于调用的代码需要知道它被查询的是什么类型的产品,所以它需要知道接口的实现者。由于使用接口的部分原因是为了隐藏这些知识,因此在这种情况下不应该使用接口是一个很好的迹象。

          一个更好的主意可能是传递ProductId 或类似的东西。现在,您可以让您的界面包含GetProductId() 方法。但是,如果被调用者真的想要一个ProductId,那么只给他他想要的东西就更解耦了,而不是他可以使用得到他真正想要的东西。

          【讨论】:

            【解决方案6】:

            我遇到了完全相同的问题。我有

            class Bar : IFin    // IFin is interface
            {
            }
            

            将 List 传递给一个期望 List 的函数被证明是一件令人头疼的事情(不明白为什么......毕竟 Bar 是在 IFin 中实现 all 的!!)。

            我最终使用了 List.ConvertAll 和下面的转换函数:

            public static IFin ToIFin(Bar_bar)
            {
                        return (IFin)_bar;
            }
            

            我知道我有时会错过一些非常明显的事情,但对我来说,尽管那个箍看起来很愚蠢,但我还是会跳起来。我知道这并不优雅,它确实会再次生成一个完整的 IFi 列表,但至少新列表仍然包含我原来的 Bar 对象(我确实验证过)。

            【讨论】:

              猜你喜欢
              • 2011-04-01
              • 2020-06-14
              • 1970-01-01
              • 1970-01-01
              • 2010-09-05
              • 2020-03-22
              • 1970-01-01
              • 1970-01-01
              • 2014-05-13
              相关资源
              最近更新 更多