为啥不总是使用泛型?

Posted

技术标签:

【中文标题】为啥不总是使用泛型?【英文标题】:Why not always use Generics?为什么不总是使用泛型? 【发布时间】:2010-10-28 07:37:40 【问题描述】:

我正在阅读 SO 上关于泛型的现有帖子。如果泛型有很多优点,比如类型安全,没有装箱/拆箱的开销,而且速度很快,为什么不总是使用它呢?为什么你会改用非泛型对象?

已编辑(问题在下面进一步扩展)

我有点困惑。上一次读泛型是几个月前,读到如果参数中的类型是可变的,应该使用泛型以防止错误。不过,我现在似乎正在阅读的是,泛型将实现限制为固定类型,而非泛型对象允许您在运行时定义参数类型。

请帮我看看我错过了什么?

其次,当您在一个团队中工作并且您的代码是可共享的时,在适当的 OOP 设计(泛型等)中使用这些类型的构造很有帮助。对于一个单独的小规模程序员来说,谁知道参数中必须包含什么类型,似乎没有必要担心,使用泛型或非泛型类型之间几乎没有区别。这准确吗?

【问题讨论】:

重新编辑:“非通用事物允许在运行时定义参数类型。” - 不,没有要定义的参数。如果有的话,那句话就是反语; 非泛型但类型化参数将您限制为一个类型,其中-作为泛型参数让调用者选择-因此泛型具有优势那里。否则,您主要限于object 关于团队与个人的观点;我根本看不出有什么不同的原因。除非你故意让你的方法不那么清晰?如果是这样,您也可以将它们全部重命名为void A(object a, object b, object c)。更好的看待它的方法很简单:这种方法是否适用于特定类型,或者相同的逻辑是否可以用于任意类型?如果是后者,泛型是一个不错的选择。 【参考方案1】:

通常您应该 - 但是,有时(尤其是在编写库代码时)不可能(或者肯定不方便)知道调用类型(即使是在 @ 987654321@),所以非泛型类型(主要是IListIEnumerable等接口)非常有用。数据绑定就是一个很好的例子。实际上,使用泛型通常会使涉及反射的任何事情变得更加困难,并且由于(通过反射的性质)您已经失去这些好处,您也可以这样做只是下降到非通用代码。反射和泛型并不是很好的朋友

【讨论】:

@Mark 你能举个例子吗? @Tymek 我已经提到过反射和数据绑定。您能更具体地说明您在寻找什么吗? @MarcGravell 是的,对不起。您能否举个例子,即使以 T 表示也不方便了解调用类型? @Tymek 我们已经有了明显的数据绑定(想想.DataSource,你只是传递了object 作为列表);非齐次集合就是一个很好的例子。另一个是代码遍历对象结构的任何东西(可能是序列化) - 在这里,任何T 要么只是最外面的节点(没有太大帮助),要么你必须为每个节点都有一个TSomething整个模型中的成员/子成员/列表/等(不漂亮);同样,调用GetType() 并使用Type 比调用MakeGenericMethod() 使用T 要容易得多。 @MarcGravell 谢谢。【参考方案2】:

泛型可能快速且类型安全,但也增加了复杂性(另一个维度可能会有所不同,程序员必须理解)。谁来维护你的代码?并非所有使用泛型(或 lambdas,或依赖注入,或...)的技巧都是值得的。想想你要解决什么问题,以及该问题的哪些部分将来可能会改变。针对这些情况进行设计。最灵活的软件过于复杂,无法由普通程序员维护。

【讨论】:

一些有效的 general 点,但我不确定我是否能看到泛型在这里增加了很多复杂性......事实上,在很多方面很难弄错使用泛型(编译器会对你发牢骚)。 马克,你的上下文不是很清楚。添加设计元素总是会增加复杂性。例如,另一层间接性本身并没有增加很多复杂性,但所有这些都加起来了。问题是添加的元素是否值得。在编写代码时弄错泛型是一回事:理解你必须维护的代码是另一回事。如果你有一个业务层 Order 类,在什么情况下你会使其通用?对于什么参数?不幸的是,答案是:它取决于 +1 很好的答案。编码时最重要的考虑因素之一是某种设计的好处是否超过了增加的复杂性所带来的成本。【参考方案3】:

有时“对象”集合是不可避免的。当同一个控件/集合中有多种类型时,通常会发生这种情况 - 它们的唯一共同类型是“对象”,因此这是您的集合的最佳类型。

使用 PropertyGrid 可以看到不时弹出的对象(非集合相关)的另一种情况。第三方属性网格可能允许您附加一个“验证器”,它返回用户对网格上给定属性的新值是否可接受。由于 PropertyGrid 不知道它将显示什么属性,因此它可以为验证器提供的最好的就是一个对象——即使验证器确切地知道将使用什么类型调用它。

但根据 Mark 的回答 - .NET 中的大多数(全部?)非通用集合只是出于遗留原因。如果今天重新制作 .NET,您可以确定标准库看起来会大不相同。

【讨论】:

@Mark:我长久以来的疑虑解除了。很高兴知道非通用的东西只是为了遗留原因。 @RPK:实际上,非泛型的东西还有其他一些优点。例如,如果类 SuperCollection 实现了 IList,但也实现了只读的非泛型 ICollection,并且 SuperCollection 被传递给接受 IEnumerable 并调用 Count 扩展的例程方法,该 Count 方法将能够使用 ICollection.Count 而无需枚举列表中的项目。【参考方案4】:

泛型是一般的方法。增加的复杂性可能是反对它的一个论据,但 imo 完全值得考虑 在大多数情况下的好处。作为一般经验法则,接受它。反射是泛型使其更难的另一种情况。但我只有在没有意义的情况下才避免使用泛型。

我喜欢避免使用泛型的一种情况是将方法设为泛型,因为它没有泛型的预期目的并且可能会产生误导。 引入泛型是为了更好的类型安全和性能(当涉及到值类型时)。如果两者都没有,那么通用方法可能会误导开发人员。例如考虑来自this question的sn-p:

public void DoSomething<T>(T request)

    if (request == null) 
        throw new ArgumentNullException("request");

    if (request is ISomeInterface)
        DoSomething();
    else
        DoSomethingElse();

上面的方法一般没有处理任何事情。当您在泛型方法中进行大量类型检查时,情况会更糟:

public void DoSomething<T>(T request)

    if (typeof(T) == typeof(X))
        DoSomething();
    else if (typeof(T) == typeof(Y))
        DoSomethingElse();
    ...

这完全违背了泛型的逻辑。有更好的模式可以避免类型检查,但如果绝对有必要,那么接受object 作为参数是我更喜欢的。另一个类似的情况是值类型无论如何都装在泛型方法中:

public void DoSomething<T>(T request)

    var type = request.GetType(); //boxed already in case of value types.

    //or other reflection calls which involves boxing

通过查看方法定义,人们很容易想到这种方法没有装箱惩罚,但让调用者清楚地知道它是一种好方法。我喜欢这样:

public void DoSomething(object request) //boxes here

    var type = request.GetType(); 

    //or other reflection calls which involves boxing

除此之外,我一直更喜欢泛型。


两者可能表现不同的极端情况是dynamic

public void Generic<T>(T request)



public void Object(object request)



dynamic d = null;
Object(d); //no issues
Generic(d); //run-time explosion; can not infer type argument

不是一个需要考虑的因素,只是一个值得考虑的愚蠢行为差异。

【讨论】:

【参考方案5】:

泛型使代码更加可重用,增加类型安全等。如果您看到任何好的 API(Jdk、Scala 等),您将在任何地方看到泛型。 泛型的最大挑战是,习惯它有点棘手,因此许多程序员发现泛型代码不可读。

但是在这里我们应该解决真正的问题(弥合知识差距)而不是避免泛型。

【讨论】:

以上是关于为啥不总是使用泛型?的主要内容,如果未能解决你的问题,请参考以下文章

为啥在接口列表的泛型类型中使用私有嵌套类型不是“不一致的可访问性”?

泛型有啥好处,为啥要使用它们?

为啥在 Typescript 泛型中使用 '&'

为啥 C# (4.0) 不允许泛型类类型中的协变和逆变?

为啥 Java 泛型不允许对泛型类型进行类型转换?

为啥 TypeScript 中的方法链接会导致泛型类型推断失败?