简体   繁体   English

具有接口约束的通用类与实现接口的类

[英]Generic class with an interface constraint vs class implementing interface

Recently I was implementing a Trie data structure and decided the Nodes could store different types of data or have its implementation varied so then I went for Node<T> . 最近我实现了一个Trie数据结构,并确定节点可以存储不同类型的数据或者实现变化,然后我去了Node<T> Then as I got into the algorithm for constructing the Trie I realised it required more intimate knowledge of the Node so I constrained the generic class to use an INode interface. 然后当我进入构建Trie的算法时,我意识到它需要更多关于Node的知识,所以我限制泛型类使用INode接口。 This allows for more flexibility but felt wrong in the context of a generic class. 这允许更大的灵活性,但在泛型类的上下文中感觉错误。

Generic classes have a different use case to classes which implement an interface. 通用类对实现接口的类具有不同的用例。 For example, List<T> - the algorithm can work without being dependent on a related set of abstractions. 例如, List<T> - 算法可以在不依赖于相关抽象集的情况下工作。 A class which implements an interface may require polymorphism/DI but the interfaces will be more specialized. 实现接口的类可能需要多态/ DI,但接口将更加专业化。

Under what circumstances do others apply a generic class T where T may implement a more specialized interface? 在什么情况下其他人应用泛型类T,其中T可以实现更专业的接口?

I thought that a generic class is used when T does not really need to expose operations/data though I can see a generic class may be used where T implements IDisposable or some other more general interface. 我认为当T不需要公开操作/数据时会使用泛型类,虽然我可以看到在T实现IDisposable或其他更通用的接口时可以使用泛型类。

Any help in clarifying these points? 澄清这些要点有什么帮助吗?

When faced with a choice to use a generic with an interface constraint vs. a non-generic with an interface type, I would go for generic+interface only in situations when some or all of types passed as generic arguments are value types. 当面临选择使用具有接口约束的泛型而非使用接口类型的非泛型时,我将仅在通用作为泛型参数传递的某些或所有类型为值类型的情况下使用泛型+接口。 This would prevent my implementation from requiring costly boxing and unboxing when dealing with my struct s. 这将阻止我的实现在处理我的struct时需要昂贵的装箱和拆箱。

For example, if the interface happens to be IComparable , I wold definitely prefer a generic with a constraint, because it would let me avoid boxing when working with primitives. 例如,如果接口恰好是IComparable ,我肯定更喜欢带有约束的泛型,因为它可以让我在使用基元时避免装箱。

Note that an alternative way of providing functionality to your generic class is passing a delegate along with the value. 请注意,为泛型类提供功能的另一种方法是将委托与值一起传递。 For example, if you plan to do something like this 例如,如果您打算做这样的事情

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

you can also do this: 你也可以这样做:

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);
        ...
    }
}

The second solution lets you "decouple" the scoring functionality from the type being scored, at the expense of having the caller to write a little more code. 第二种解决方案允许您将评分功能与正在评分的类型“解耦”,但代价是让调用者编写更多代码。

I see nothing wrong with placing constraints on the generic argument. 我认为在泛型参数上放置约束没有错。 Having a generic argument does not imply "this will work for anything", it implies that there is more than one way that the code will make sense. 拥有泛型参数并不意味着“这将适用于任何事物”,这意味着代码有多种方式可以理解。

It might actually expose a completely generic concept, like List<T> , but it might expose a concept that makes sense only in some contexts (like Nullable<T> only making sense for non-nullable entities) 它可能实际上暴露了一个完全通用的概念,比如List<T> ,但它可能会暴露一个仅在某些上下文中有意义的概念(比如Nullable<T>仅对非可空实体有意义)

The constraints are just that mechanism that you use to tell the world under what circumstances the class will make sense, and will enable you to actually use that (constrained) argument in a reasonable way, ie calling Dispose on things that implement IDisposable 约束只是用于告诉世界在什么情况下类将有意义的机制,并且将使您能够以合理的方式实际使用该(约束的)参数,即在实现IDisposable事物上调用Dispose

The extreme of this is when the context is very constrained, ie what if there are only two possible implementations? 这种情况的极端情况是当上下文非常有限时,即如果只有两种可能的实现呢? I actually have that case in my current codebase, and I use generics. 我实际上在我当前的代码库中有这种情况,我使用泛型。 I need some processing done on some data point, and currently (and in the foreseeable future) there are only two kinds of data points. 我需要对某些数据点进行一些处理,目前(在可预见的将来),只有两种数据点。 This is, in principle, the code I use: 原则上,这是我使用的代码:

interface IDataPoint 
{ 
   SomeResultType Process();
}

class FirstKindDataPoint : IDataPoint 
{
   SomeResultType Process(){...}
};

class SecondKindDataPoint : IDataPoint 
{
   SomeResultType Process(){...}
};

class DataPointProcessor<T> where T: IDataPoint
{
   void AcquireAndProcessDataPoints(){...}
}

It makes sense, even in this constrained context, because I have only one processor, so only one logic to take care of, instead of two separate processor that I will have to try to keep in sync. 这是有意义的,即使在这种受限制的环境中,因为我只有一个处理器,所以只需要一个逻辑来处理,而不是两个独立的处理器,我将不得不尝试保持同步。

This way I can have a List<T> and an Action<T> within the processor instead of a List<IDataPoint> and Action<IDataPoint> which will be incorrect in my scenario, as I need a processor for a more specific data type, that is still, implementing IDataPoint . 这样我可以在处理器中使用List<T>Action<T>而不是List<IDataPoint>Action<IDataPoint> ,这在我的场景中是不正确的,因为我需要一个处理器来获得更具体的数据类型,那仍然是,实施IDataPoint

If I needed a processor that will process anything, as long as it is an IDataPoint , it might make sense to remove the its genericity, and simply use IDataPoint within the code. 如果我需要一个可以处理任何东西的处理器,只要它是一个IDataPoint ,删除它的通用性就可以了,只需在代码中使用IDataPoint即可。

Additionally, the point raised in @dasblinkenlight's answer is very valid. 此外,@ dasblinkenlight的回答中提出的观点非常有效。 If the generic parameters can be both structs and classes than using generics will avoid any boxing. 如果泛型参数可以是结构和类,而不是使用泛型将避免任何装箱。

Generics are usually used where using an interface or a base class (and this includes object ) are not enough, for example where you are worried about the return value's of your function being the original type rather than just the interface, or where the parameters you are passing in may be expressions that operate on the specific type. 泛型通常用于使用接口或基类(这包括object )是不够的,例如,您担心函数的返回值是原始类型而不仅仅是接口,或者参数在哪里传入可能是对特定类型进行操作的表达式。

So if you approach the logic from the other end. 所以如果你从另一端接近逻辑。 The decisions on type restrictions should be the same decision as when you are choosing the types of your function parameters. 类型限制的决定应与选择函数参数类型时的决策相同。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM