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

本文关键字:接口 实现 泛型类 约束 | 更新日期: 2023-09-27 18:33:54

>最近我正在实现一个Trie数据结构,并决定节点可以存储不同类型的数据或使其实现方式不同,所以我选择了Node<T>。然后,当我进入构造 Trie 的算法时,我意识到它需要更深入的 Node 知识,所以我将泛型类限制为使用 INode 接口。这允许更大的灵活性,但在泛型类的上下文中感觉是错误的。

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

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

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

对澄清这些要点有什么帮助吗?

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

当面临使用具有接口约束的泛型与具有接口类型的非泛型的选择时,只有在作为泛型参数传递的部分或全部类型是值类型的情况下,我才会选择 generic +interface。这将防止我的实现在处理我的struct时需要昂贵的装箱和拆箱。

例如,如果接口恰好是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);
        ...
    }
}

第二种解决方案允许您将评分功能与要评分的类型"分离",但代价是让调用方编写更多代码。

我认为对

通用参数进行约束没有错。拥有通用参数并不意味着"这将适用于任何事情",它意味着代码有意义的方式不止一种。

它实际上可能会公开一个完全通用的概念,如List<T>,但它可能会公开一个仅在某些上下文中有意义的概念(例如Nullable<T>只对不可为空的实体有意义(

约束只是你用来告诉世界在什么情况下类有意义的机制,并将使您能够以合理的方式实际使用该(约束的(参数,即对实现IDisposable的东西调用Dispose

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

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

即使在这种受限的上下文中,这也是有道理的,因为我只有一个处理器,所以只有一个逻辑需要处理,而不是我必须尝试保持同步的两个独立处理器。

这样,我可以在处理器中有一个List<T>和一个Action<T>,而不是在我的场景中不正确的List<IDataPoint>Action<IDataPoint>,因为我需要一个更具体的数据类型的处理器,即仍然实现IDataPoint

如果我需要一个可以处理任何东西的处理器,只要它是一个IDataPoint,删除它的通用性,并在代码中使用IDataPoint可能是有意义的。

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

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

因此,如果您从另一端处理逻辑。有关类型限制的决策应与选择函数参数类型时的决定相同。