代码之家  ›  专栏  ›  技术社区  ›  Jaycee

具有接口约束的泛型类与实现接口的类

  •  3
  • Jaycee  · 技术社区  · 12 年前

    最近我正在实施 Trie 数据结构,并决定节点可以存储不同类型的数据或使其实现多样化,所以我选择 Node<T> 。然后,当我进入构建Trie的算法时,我意识到它需要对Node有更深入的了解,所以我约束泛型类使用 INode 界面这允许更大的灵活性,但在泛型类的上下文中感觉是错误的。

    泛型类与实现接口的类具有不同的用例。例如 List<T> -该算法可以在不依赖于相关抽象集的情况下工作。实现接口的类可能需要多态性/DI,但接口将更加专业化。

    其他人在什么情况下应用泛型类T,其中T可以实现更专业的接口?

    我认为当T并不真正需要公开操作/数据时会使用泛型类,尽管我可以看到当T实现IDisposable或其他更通用的接口时可以使用泛型类。

    澄清这些问题有什么帮助吗?

    3 回复  |  直到 12 年前
        1
  •  5
  •   Sergey Kalinichenko    12 年前

    当面临使用带有接口约束的泛型与使用带有接口类型的非泛型的选择时,只有在作为泛型参数传递的某些或所有类型都是值类型的情况下,我才会选择泛型+接口。这将防止我的实现在处理我的 struct s

    例如,如果接口恰好 IComparable ,我肯定更喜欢带约束的泛型,因为它可以让我在处理基元时避免装箱。

    请注意,为泛型类提供功能的另一种方法是将委托与值一起传递。例如,如果你计划做这样的事情

    interface IScoreable {
        decimal GetScore(object context);
    }
    class Node<T> where T : IScoreable {
        ...
        void DoSomething(T data) {
            var score = data.GetScore(someContext);
            ...
        }
    }
    

    您也可以这样做:

    class Node<T> {
        private Func<T,object,decimal> scorer;
        public Node(Func<T,object,decimal> scorer) {
            this.scorer = scorer;
        }
        ...
        void DoSomething(T data) {
            var score = scorer(data, someContext);
            ...
        }
    }
    

    第二种解决方案允许您将评分功能与正在评分的类型“解耦”,代价是让调用者再写一点代码。

        2
  •  4
  •   SWeko    12 年前

    我认为对一般论点施加约束没有错。有一个通用参数并不意味着“这对任何事情都有效”,它意味着代码有多种意义。

    它可能实际上暴露了一个完全通用的概念,比如 List<T> ,但它可能会暴露出一个只有在某些情况下才有意义的概念(比如 Nullable<T> 仅对不可为null的实体有意义)

    约束只是一种机制,用于告诉世界在什么情况下类是有意义的,并使您能够以合理的方式实际使用(受约束的)参数,即调用 Dispose 关于实现的事情 IDisposable

    这种情况的极端是当上下文非常受限时,即如果只有两种可能的实现怎么办?事实上,我在当前的代码库中有这种情况,并且我使用泛型。我需要对一些数据点进行一些处理,目前(以及在可预见的未来)只有两种数据点。原则上,这是我使用的代码:

    interface IDataPoint 
    { 
       SomeResultType Process();
    }
    
    class FirstKindDataPoint : IDataPoint 
    {
       SomeResultType Process(){...}
    };
    
    class SecondKindDataPoint : IDataPoint 
    {
       SomeResultType Process(){...}
    };
    
    class DataPointProcessor<T> where T: IDataPoint
    {
       void AcquireAndProcessDataPoints(){...}
    }
    

    即使在这种受约束的情况下,这也是有意义的,因为我只有一个处理器,所以只有一个逻辑需要处理,而不是两个单独的处理器,我必须努力保持同步。

    这样我就可以有一个 列表<T> 和一个 Action<T> 在处理器中,而不是 List<IDataPoint> Action<IDataPoint> 这在我的场景中是不正确的,因为我需要一个用于更特定数据类型的处理器,也就是说,实现 IDataPoint .

    如果我需要一个处理器来处理任何事情,只要它是 IDataPoint(数据点) ,删除其泛型并简单地使用 IDataPoint(数据点) 在代码中。

    此外,@dasblinkenlight的回答中提出的观点是非常有效的。如果泛型参数可以是结构和类,那么使用泛型将避免任何装箱。

        3
  •  2
  •   Bob Vale    12 年前

    泛型通常用于使用接口或基类(这包括 object )不够,例如,当您担心函数的返回值是原始类型而不仅仅是接口时,或者您传递的参数可能是对特定类型进行操作的表达式时。

    所以,如果你从另一端接近逻辑。关于类型限制的决定应该与选择函数参数类型时的决定相同。