一尘不染

C# 和 Java 中的泛型...和 ​​C++ 中的模板有什么区别?

c#

我主要使用 Java,泛型相对较新。我一直在读到 Java 做出了错误的决定,或者 .NET 有更好的实现等等。

那么,C++、C#、Java 在泛型中的主要区别是什么?每个的优点/缺点?


阅读 144

收藏
2022-05-05

共1个答案

一尘不染

C# 泛型允许你声明这样的东西。

List<Person> foo = new List<Person>();

然后编译器会阻止你把不在Person列表中的东西放进去。
在幕后,C# 编译器只是将List<Person>.NET dll 文件放入其中,但在运行时 JIT 编译器会构建一组新代码,就好像您编写了一个特殊的列表类只是为了包含人一样 - 类似于ListOfPerson.

这样做的好处是它使它非常快。没有强制转换或任何其他内容,并且由于 dll 包含这是 List 的信息,Person因此稍后使用反射查看它的其他代码可以判断它包含Person对象(因此您获得智能感知等)。

这样做的缺点是旧的 C# 1.0 和 1.1 代码(在他们添加泛型之前)不理解这些新List<something>的,所以你必须手动将东西转换回普通的旧代码List才能与它们互操作。这不是什么大问题,因为 C# 2.0 二进制代码不向后兼容。唯一会发生这种情况的情况是,如果您将一些旧的 C# 1.0/1.1 代码升级到 C# 2.0

Java 泛型允许你声明这样的东西。

ArrayList<Person> foo = new ArrayList<Person>();

从表面上看,它看起来是一样的,而且有点像。编译器还会阻止您将不在列表中的内容Person放入列表中。

不同之处在于幕后发生的事情。与 C# 不同,Java 并没有去构建一个特殊的ListOfPerson- 它只是使用ArrayListJava 中一直存在的普通旧的。当您从阵列中取出东西时,Person p = (Person)foo.get(1);仍然必须完成通常的铸造舞蹈。编译器正在为您节省按键,但仍然会像往常一样产生速度命中/投射。
当人们提到“类型擦除”时,这就是他们所说的。编译器会为你插入演员表,然后“抹去”这个事实,即它Person不仅仅是一个列表Object

这种方法的好处是不理解泛型的旧代码不必关心。它仍在处理与ArrayList往常一样的旧事物。这在 Java 世界中更为重要,因为他们希望支持使用带有泛型的 Java 5 编译代码,并让它在旧的 1.4 或以前的 JVM 上运行,微软故意决定不打扰。

缺点是我之前提到的速度下降,而且因为没有ListOfPerson伪类或类似的东西进入 .class 文件,稍后查看它的代码(通过反射,或者如果你将它从另一个集合中拉出来它被转换成的地方Object等等)不能以任何方式告诉它是一个列表,它只包含Person而不是任何其他数组列表。

C++ 模板允许你声明这样的东西

std::list<Person>* foo = new std::list<Person>();

它看起来像 C# 和 Java 泛型,它会做你认为它应该做的事情,但在幕后发生了不同的事情。

它与 C# 泛型的最共同之处在于它构建了特殊pseudo-classes的而不是像 java 那样仅仅丢弃类型信息,但它是一个完全不同的鱼锅。

C# 和 Java 都产生专为虚拟机设计的输出。如果您编写的代码中包含一个Person类,那么在这两种情况下,关于一个Person类的一些信息都将进入 .dll 或 .class 文件,而 JVM/CLR 将对此进行处理。

C++ 生成原始 x86 二进制代码。一切都不是对象,并且没有需要了解Person类的底层虚拟机。没有装箱或拆箱,函数不必属于类或任何东西。

因此,C++ 编译器对您可以使用模板执行的操作没有任何限制——基本上任何您可以手动编写的代码,您都可以获取模板来为您编写。
最明显的例子是添加东西:

在 C# 和 Java 中,泛型系统需要知道类可用的方法,并且需要将其传递给虚拟机。告诉它的唯一方法是硬编码实际的类,或者使用接口。例如:

string addNames<T>( T first, T second ) { return first.Name() + second.Name(); }

该代码不会在 C# 或 Java 中编译,因为它不知道该类型T实际上提供了一个名为 Name() 的方法。你必须告诉它 - 在 C# 中是这样的:

interface IHasName{ string Name(); };
string addNames<T>( T first, T second ) where T : IHasName { .... }

然后你必须确保你传递给 addNames 的东西实现了 IHasName 接口等等。java 语法不同(<T extends IHasName>),但也有同样的问题。

这个问题的“经典”案例是尝试编写一个执行此操作的函数

string addNames<T>( T first, T second ) { return first + second; }

您实际上无法编写此代码,因为无法使用其中的+方法声明接口。你失败了。

C++ 没有遇到这些问题。编译器不关心将类型传递给任何 VM - 如果您的两个对象都有 .Name() 函数,它将编译。如果他们不这样做,就不会。简单的。

2022-05-05