34

我的理解是,在 C# 中为泛型指定方差发生在类型声明级别:当您创建泛型类型时,您指定类型参数的方差。另一方面,在 Java 中,在使用泛型的地方指定了方差:当您创建某个泛型类型的变量时,您指定其类型参数如何变化。

每个选项的优缺点是什么?

4

4 回答 4

39

我只是要回答声明站点和使用站点差异之间的差异,因为尽管 C# 和 Java 泛型在许多其他方面有所不同,但这些差异大多与差异正交。

首先,如果我没记错的话,使用站点差异严格来说比声明站点差异更强大(尽管以简洁为代价),或者至少 Java 的通配符是(实际上比使用站点差异更强大)。这种增强的功能对于大量使用有状态构造的语言特别有用,例如 C# 和 Java(但 Scala 则少得多,特别是因为它的标准列表是不可变的)。考虑List<E>(或IList<E>)。由于它具有添加 E 和获取 E 的方法,因此它相对于 E 是不变的,因此不能使用声明站点方差。但是,对于使用站点方差,您可以只说List<+Number>获取 的协变子集ListList<-Number>获取 的逆变子集List. 在声明站点语言中,库的设计者必须为每个子集创建单独的接口(或类,如果您允许类的多重继承)并List扩展这些接口。如果库设计者不这样做(请注意,C#IEnumerable只做 的协变部分的一小部分IList),那么你就不走运了,你必须求助于在没有任何类型的语言中必须做的同样的麻烦的方差。

这就是使用站点继承相对于声明站点继承的优势。声明点继承相对于使用点继承的优势基本上对用户来说是简洁的(假设设计者努力将每个类/接口分成协变和逆变部分)。对于类似IEnumerableor的东西Iterator,不必在每次使用接口时都指定协方差是很好的。Java 通过使用冗长的语法使这变得特别烦人(Java 的解决方案基本上是理想的双变量除外)。

当然,这两种语言特性可以共存。对于自然协变或逆变的类型参数(例如 in IEnumerable/ Iterator),请在声明中声明。对于自然不变的类型参数(例如 in (I)List),每次使用时声明你想要什么样的变化。只是不要为具有声明站点差异的参数指定使用站点差异,因为这只会使事情变得混乱。

还有其他更详细的问题我没有涉及(例如通配符实际上比使用站点差异更强大),但我希望这能回答您对内容的问题。我承认我偏向于使用站点的差异,但我试图描绘在我与程序员和语言研究人员的讨论中出现的两者的主要优势。

于 2011-03-29T22:19:39.463 回答
16

大多数人似乎更喜欢声明站点差异,因为它使库用户更容易(同时使库开发人员更难,尽管我认为库开发人员必须考虑差异,无论差异在哪里实际上是写的。)

但请记住,Java 和 C# 都不是良好语言设计的示例。

尽管由于 Java 5 中兼容的 VM 改进和类型擦除, Java获得了正确的方差并且独立于 JVM 工作,但使用站点的方差使使用变得有点麻烦,并且类型擦除的特定实现受到了当之无愧的批评。

C#的声明站点方差模型减轻了库用户的负担,但是在他们引入具体泛型期间,他们基本上将方差规则构建到了他们的 VM 中。即使在今天,由于这个错误,他们也不能完全支持协变/逆变(并且不向后兼容的具体集合类的引入将程序员分成了两个阵营)。

这对所有针对 CLR 的语言构成了严格的限制,这也是为什么替代编程语言在 JVM 上更加活跃的原因之一,尽管 CLR 似乎具有“更好的特性”。

让我们看看Scala:Scala 是一个运行在 JVM 上的完全面向对象的功能混合体。他们像 Java 一样使用类型擦除,但是泛型的实现和(声明站点)变体都比 Java(或 C#)更容易理解、更直接、更强大,因为 VM 没有规定变体必须如何处理的规则工作。Scala 编译器检查方差表示法,可以在编译时拒绝不健全的源代码,而不是在运行时抛出异常,而生成的 .class 文件可以在 Java 中无缝使用。

声明站点差异的一个缺点是,在某些情况下,它似乎使类型推断变得更加困难。

同时,Scala 可以使用原始类型,而无需像在 C# 中那样将它们装箱到集合中,方法是使用@specialized注释告诉 Scala 编译器生成专用于请求的原始类型的类或方法的一个或多个附加实现。

Scala 还可以“几乎”通过使用 Manifest 来具体化泛型,这允许它们在运行时检索泛型类型,就像在 C# 中一样。

于 2010-11-27T16:06:27.427 回答
1

Java风格泛型的缺点

一个后果是 java 版本仅适用于引用类型(或装箱值类型)而不适用于值类型。IMO 这是最大的缺点,因为它在许多场景中阻止了高性能泛型,并且需要手动编写专门的类型。

它不能保证像“此列表仅包含 x 类型的对象”这样的不变量,并且需要在每个 getter 处进行运行时检查。泛型类型确实存在。

使用反射时,您不能询问泛型对象的实例它具有哪些泛型参数。

Java 风格泛型的优点

您会得到方差/可以在不同的通用参数之间进行转换。

于 2010-11-27T16:14:59.660 回答
0

Java:自 Java 5 以来的使用站点方差泛型。自 1.0 以来具有不同语法的损坏的协变数组。没有泛型的运行时类型信息。

C#:自 C# 2.0 起使用站点变体泛型。在 C# 4.0 中添加了声明站点差异。自 1.0 以来具有不同语法的损坏的协变数组(与 Java 相同的问题)。“reified”泛型意味着类型信息在编译时不会丢失。

Scala:自该语言的早期版本以来(至少自 2008 年以来),使用站点/声明站点都存在差异。数组不是单独的语言功能,因此您使用相同的泛型语法和类型变化规则。某些集合是在 VM 级别使用 JVM 数组实现的,因此与 Java 代码相比,您可以获得相同或更好的运行时性能。

详细说明 C#/Java 数组类型安全问题:您可以将 Dog[] 强制转换为 Pet[] 并添加 Cat 并触发编译时未捕获的运行时错误。Scala 正确地实现了这一点。

于 2013-09-10T05:25:21.143 回答