我会添加我的声音的噪音,并采取刺刀使事情清楚:
C#泛型允许你声明这样的东西。
List foo = new List();
然后编译器将阻止你把不是Person的东西放到列表中。
在后台,C#编译器只是将List进入.NET dll文件,但在运行时JIT编译器会构建一组新的代码,就好像你编写了一个专门用于包含人的列表类 – 像ListOfPerson。
这样做的好处是它使它真的很快。没有投射或任何其他的东西,并且因为dll包含的信息,这是一个人的列表,其他代码,看看它后来使用反射可以告诉它包含Person对象(所以你得到intellisense等)。
这样做的缺点是旧的C#1.0和1.1代码(在他们添加泛型之前)不理解这些新的List< something> ;,所以你必须手动将事情转换回旧的List与它们互操作。这不是一个大问题,因为C#2.0二进制代码不向后兼容。只有这样,如果你升级一些旧的C#1.0 / 1.1代码到C#2.0
Java泛型允许你声明这样的东西。
ArrayList foo = new ArrayList();
在表面上看起来是一样的,它的排序是。编译器还会阻止你把不是Person的东西放到列表中。
区别是幕后发生了什么。与C#不同,Java不会去构建一个特殊的ListOfPerson – 它只是使用一直在Java中的普通的ArrayList。当你从数组中取出东西时,通常的Person p =(Person)foo.get(1);铸造舞蹈还有待完成。编译器会为你保存按键,但速度命中/投射仍然会发生,就像它总是。
当人们提到“类型擦除”,这是他们在说什么。编译器为你插入转换,然后“擦除”它的意思是一个Person列表的事实,而不仅仅是Object
这种方法的好处是,不理解泛型的旧代码不必关心。它仍然处理相同的旧ArrayList,因为它总是有。这在Java世界中更为重要,因为他们希望支持使用带有泛型的Java 5编译代码,并让它在旧的1.4或以前的JVM上运行,微软故意决定不去处理。
缺点是我之前提到的速度命中,也因为没有ListOfPerson伪类或类似的任何东西进入.class文件,代码,以后看它(用反射,或者如果你把它从另一个集合,它被转换为对象等)不能以任何方式告诉它的意思是一个只包含Person而不只是任何其他数组列表的列表。
C模板允许你声明这样的东西
std::list* foo = new std::list();
它看起来像C#和Java泛型,它会做你认为应该做的,但在幕后不同的事情发生。
它与C#泛型最常见的是它构建特殊的伪类,而不是像java那样丢弃类型信息,但是它是一个完全不同的鱼。
C#和Java都生成为虚拟机设计的输出。如果你写了一些具有Person类的代码,在这两种情况下,一个Person类的一些信息将会进入.dll或.class文件,JVM / CLR会做这件事。
C产生原始x86二进制代码。一切都不是一个对象,没有底层的虚拟机,需要知道一个Person类。没有拳击或开箱,并且功能不必属于类,或任何东西。
因此,C编译器没有限制你可以用模板做什么 – 基本上任何可以手动编写的代码,你可以得到模板来为你写。
最明显的例子是添加东西:
在C#和Java中,泛型系统需要知道类的可用方法,并且需要将其传递给虚拟机。告诉它的唯一方法是通过硬编码实际的类,或使用接口。例如:
string addNames( T first, T second ) { return first.Name() + second.Name(); }
该代码不会在C#或Java中编译,因为它不知道类型T实际上提供了一个名为Name()的方法。你必须告诉它 – 在C#中像这样:
interface IHasName{ string Name(); };
string addNames( T first, T second ) where T : IHasName { .... }
然后你必须确保你传递给addNames的东西实现IHasName接口等等。 java语法是不同的(),但它遇到相同的问题。
这个问题的“经典”情况是试图写一个这样做的函数
string addNames( T first, T second ) { return first + second; }
你实际上不能写这段代码,因为没有办法用它的方法声明一个接口。你失败了。
C没有这些问题。编译器不关心传递类型到任何虚拟机 – 如果你的对象有一个.Name()函数,它会编译。如果他们不,它不会。简单。
所以你有它 :-)