C# 和 Java 中的泛型与 C++ 中的模板有什么区别? [英] What are the differences between Generics in C# and Java... and Templates in C++?

查看:29
本文介绍了C# 和 Java 中的泛型与 C++ 中的模板有什么区别?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

那么,C++、C#、Java 在泛型方面的主要区别是什么?各自的优缺点?

解决方案

我会在噪音中加入我的声音并尝试使事情变得清晰:

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

Listfoo = new List();

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

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

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

Java 泛型允许您声明类似的内容.

ArrayListfoo = new ArrayList();

从表面上看,它看起来是一样的,实际上是一样的.编译器还会阻止您将不是 Person 的内容放入列表中.

不同之处在于幕后发生的事情.与 C# 不同,Java 不会构建特殊的 ListOfPerson - 它只是使用 Java 中一直存在的普通的旧 ArrayList.当你从数组中取出东西时,通常的 Person p = (Person)foo.get(1); cast-dance 仍然必须完成.编译器为您节省了按键次数,但仍然会像往常一样产生命中/投射速度.
当人们提到类型擦除"时这就是他们所说的.编译器为您插入强制转换,然后擦除"它是 Person 的列表而不仅仅是 Object

的事实

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

缺点是我之前提到的速度下降,而且因为没有 ListOfPerson 伪类或任何类似的东西进入 .class 文件,稍后查看它的代码(使用反射,或者如果你把它从另一个集合中拉出来,在那里它被转换成 Object 等等)不能以任何方式告诉它是一个只包含 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 ) 其中 T : IHasName { .... }

然后你必须确保你传递给 addNames 的东西实现了 IHasName 接口等等.java 语法是不同的(),但它遇到了同样的问题.

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

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

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

C++ 没有这些问题.编译器不关心将类型传递给任何虚拟机——如果你的两个对象都有一个 .Name() 函数,它就会编译.如果他们不这样做,就不会.简单.

所以,你有它:-)

I mostly use Java and generics are relatively new. I keep reading that Java made the wrong decision or that .NET has better implementations etc. etc.

So, what are the main differences between C++, C#, Java in generics? Pros/cons of each?

解决方案

I'll add my voice to the noise and take a stab at making things clear:

C# Generics allow you to declare something like this.

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

and then the compiler will prevent you from putting things that aren't Person into the list.
Behind the scenes the C# compiler is just putting List<Person> into the .NET dll file, but at runtime the JIT compiler goes and builds a new set of code, as if you had written a special list class just for containing people - something like ListOfPerson.

The benefit of this is that it makes it really fast. There's no casting or any other stuff, and because the dll contains the information that this is a List of Person, other code that looks at it later on using reflection can tell that it contains Person objects (so you get intellisense and so on).

The downside of this is that old C# 1.0 and 1.1 code (before they added generics) doesn't understand these new List<something>, so you have to manually convert things back to plain old List to interoperate with them. This is not that big of a problem, because C# 2.0 binary code is not backwards compatible. The only time this will ever happen is if you're upgrading some old C# 1.0/1.1 code to C# 2.0

Java Generics allow you to declare something like this.

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

On the surface it looks the same, and it sort-of is. The compiler will also prevent you from putting things that aren't Person into the list.

The difference is what happens behind the scenes. Unlike C#, Java does not go and build a special ListOfPerson - it just uses the plain old ArrayList which has always been in Java. When you get things out of the array, the usual Person p = (Person)foo.get(1); casting-dance still has to be done. The compiler is saving you the key-presses, but the speed hit/casting is still incurred just like it always was.
When people mention "Type Erasure" this is what they're talking about. The compiler inserts the casts for you, and then 'erases' the fact that it's meant to be a list of Person not just Object

The benefit of this approach is that old code which doesn't understand generics doesn't have to care. It's still dealing with the same old ArrayList as it always has. This is more important in the java world because they wanted to support compiling code using Java 5 with generics, and having it run on old 1.4 or previous JVM's, which microsoft deliberately decided not to bother with.

The downside is the speed hit I mentioned previously, and also because there is no ListOfPerson pseudo-class or anything like that going into the .class files, code that looks at it later on (with reflection, or if you pull it out of another collection where it's been converted into Object or so on) can't tell in any way that it's meant to be a list containing only Person and not just any other array list.

C++ Templates allow you to declare something like this

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

It looks like C# and Java generics, and it will do what you think it should do, but behind the scenes different things are happening.

It has the most in common with C# generics in that it builds special pseudo-classes rather than just throwing the type information away like java does, but it's a whole different kettle of fish.

Both C# and Java produce output which is designed for virtual machines. If you write some code which has a Person class in it, in both cases some information about a Person class will go into the .dll or .class file, and the JVM/CLR will do stuff with this.

C++ produces raw x86 binary code. Everything is not an object, and there's no underlying virtual machine which needs to know about a Person class. There's no boxing or unboxing, and functions don't have to belong to classes, or indeed anything.

Because of this, the C++ compiler places no restrictions on what you can do with templates - basically any code you could write manually, you can get templates to write for you.
The most obvious example is adding things:

In C# and Java, the generics system needs to know what methods are available for a class, and it needs to pass this down to the virtual machine. The only way to tell it this is by either hard-coding the actual class in, or using interfaces. For example:

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

That code won't compile in C# or Java, because it doesn't know that the type T actually provides a method called Name(). You have to tell it - in C# like this:

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

And then you have to make sure the things you pass to addNames implement the IHasName interface and so on. The java syntax is different (<T extends IHasName>), but it suffers from the same problems.

The 'classic' case for this problem is trying to write a function which does this

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

You can't actually write this code because there are no ways to declare an interface with the + method in it. You fail.

C++ suffers from none of these problems. The compiler doesn't care about passing types down to any VM's - if both your objects have a .Name() function, it will compile. If they don't, it won't. Simple.

So, there you have it :-)

这篇关于C# 和 Java 中的泛型与 C++ 中的模板有什么区别?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆