Вопрос-ответ

What are the differences between Generics in C# and Java... and Templates in C++? [closed]

В чем разница между дженериками в C # и Java ... и шаблонами в C ++?



Я в основном использую Java, а дженерики относительно новы. Я продолжаю читать, что Java приняла неправильное решение или что.NET имеет лучшие реализации и т.д. и т.п.

Итак, каковы основные различия между C ++, C #, Java в дженериках? Плюсы / минусы каждого из них?

Переведено автоматически
Ответ 1

Я добавлю свой голос к общему шуму и попытаюсь внести ясность:

Дженерики C # позволяют объявлять что-то подобное.

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

и тогда компилятор не позволит вам помещать то, чего нет Person в список.

За кулисами компилятор C # просто помещает List<Person> в .ЧИСТЫЙ dll-файл, но во время выполнения JIT-компилятор идет и создает новый набор кода, как если бы вы написали специальный класс list только для того, чтобы содержать людей - что-то вроде ListOfPerson.

Преимущество этого в том, что это делает работу действительно быстрой. Здесь нет кастинга или каких-либо других вещей, и поскольку библиотека dll содержит информацию, которая является списком Person, другой код, который просматривает ее позже с помощью отражения, может сказать, что она содержит Person объекты (таким образом, вы получаете intellisense и так далее).

Недостатком этого является то, что старый код 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 - она просто использует старое доброеArrayList, которое всегда было в Java. Когда вы извлекаете что-то из массива, все еще приходится выполнять обычную Person p = (Person)foo.get(1); процедуру кастинга. Компилятор избавляет вас от необходимости нажимать клавиши, но снижение скорости по-прежнему происходит, как и всегда.
Когда люди упоминают "Стирание типа", это то, о чем они говорят. Компилятор вставляет приведения за вас, а затем "стирает" тот факт, что это должен быть список Person не только Object

Преимущество этого подхода в том, что старый код, который не понимает дженериков, не должен беспокоиться. Он по-прежнему имеет дело с тем же старымArrayList, что и всегда. Это более важно в мире Java, потому что они хотели поддерживать компиляцию кода с использованием Java 5 с дженериками и запуск его на старой версии 1.4 или предыдущих JVM, с которыми Microsoft намеренно решила не заморачиваться.

Недостатком является снижение скорости, о котором я упоминал ранее, а также из-за того, что в файлах .class нет ListOfPerson псевдокласса или чего-либо подобного, код, который просматривает его позже (с отражением, или если вы извлекаете его из другой коллекции, где он был преобразован в 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 классе. Здесь нет упаковки или распаковки, и функции не обязательно должны принадлежать классам или чему-либо еще.

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 :-)

Ответ 2

C ++ редко использует терминологию ”дженериков". Вместо этого используется слово “шаблоны”, и оно более точное. Шаблоны описывают один метод для достижения универсального дизайна.

Шаблоны на C ++ сильно отличаются от того, что реализуют как C #, так и Java по двум основным причинам. Первая причина заключается в том, что шаблоны C ++ допускают не только аргументы типа во время компиляции, но и аргументы с постоянным значением во время компиляции: шаблоны могут быть заданы в виде целых чисел или даже сигнатур функций. Это означает, что вы можете выполнять некоторые довольно сложные вещи во время компиляции, например вычисления:

template <unsigned int N>
struct product {
static unsigned int const VALUE = N * product<N - 1>::VALUE;
};

template <>
struct product<1> {
static unsigned int const VALUE = 1;
};

// Usage:
unsigned int const p5 = product<5>::VALUE;

Этот код также использует другую отличительную особенность шаблонов C ++, а именно специализацию шаблонов. Код определяет один шаблон класса, product который имеет один аргумент value. Он также определяет специализацию для этого шаблона, которая используется всякий раз, когда аргумент принимает значение 1. Это позволяет мне определить рекурсию по определениям шаблонов. Я полагаю, что это было впервые обнаружено Андреем Александреску.

Специализация шаблонов важна для C ++, поскольку она допускает структурные различия в структурах данных. Шаблоны в целом - это средство унификации интерфейса разных типов. Однако, хотя это желательно, все типы не могут обрабатываться одинаково внутри реализации. C ++ templates учитывает это. Это почти то же самое различие, которое ООП делает между интерфейсом и реализацией с переопределением виртуальных методов.

Шаблоны C ++ необходимы для его алгоритмической парадигмы программирования. Например, почти все алгоритмы для контейнеров определены как функции, которые принимают тип контейнера в качестве шаблонного типа и обрабатывают их единообразно. На самом деле, это не совсем верно: C ++ работает не с контейнерами, а скорее с диапазонами, которые определяются двумя итераторами, указывающими на начало и за концом контейнера. Таким образом, все содержимое ограничено итераторами: begin <= элементы < end.

Использование итераторов вместо контейнеров полезно, поскольку позволяет оперировать частями контейнера, а не всем целиком.

Другой отличительной особенностью C ++ является возможность частичной специализации для шаблонов классов. Это в некоторой степени связано с сопоставлением шаблонов аргументов в Haskell и других функциональных языках. Для примера давайте рассмотрим класс, в котором хранятся элементы:

template <typename T>
class Store { … }; // (1)

Это работает для любого типа элемента. Но давайте предположим, что мы можем хранить указатели более эффективно, чем другие типы, применяя какой-то специальный трюк. Мы можем сделать это, частично специализируясь на всех типах указателей:

template <typename T>
class Store<T*> { … }; // (2)

Теперь, когда мы создаем экземпляр шаблона контейнера для одного типа, используется соответствующее определение:

Store<int> x; // Uses (1)
Store<int*> y; // Uses (2)
Store<string**> z; // Uses (2), with T = string*.
Ответ 3

Сам Андерс Хейлсберг описал различия здесь "Дженерики в C #, Java и C ++".

Ответ 4

Уже есть много хороших ответов на вопрос, в чем заключаются различия, поэтому позвольте мне изложить немного иную точку зрения и добавить почему.

Как уже объяснялось, основным отличием является стирание типов, то есть тот факт, что компилятор Java стирает универсальные типы, и они не попадают в сгенерированный байт-код. Однако возникает вопрос: зачем кому-то это делать? Это не имеет смысла! Или имеет?

Ну, и какова альтернатива? Если вы не реализуете дженерики в языке, где вы их реализуете? И ответ таков: в виртуальной машине. Что нарушает обратную совместимость.

Удаление типов, с другой стороны, позволяет смешивать универсальные клиенты с непатентованными библиотеками. Другими словами: код, который был скомпилирован на Java 5, все еще может быть развернут на Java 1.4.

Однако Microsoft решила нарушить обратную совместимость для дженериков. Вот почему дженерики .NET "лучше" дженериков Java.

Конечно, Sun не идиоты и не трусы. Причина, по которой они "струсили", заключалась в том, что Java была значительно старше и распространеннее, чем .NET, когда они представили дженерики. (Они были представлены примерно в одно и то же время в обоих мирах.) Нарушение обратной совместимости было бы огромной проблемой.

Иными словами: в Java дженерики являются частью языка (что означает, что они применимы только к Java, а не к другим языкам), в .NET они являются частью виртуальной машины (что означает, что они применимы ко всем языкам, а не только к C # и Visual Basic.NET).

Сравните это с .СЕТЕВЫЕ функции, такие как LINQ, лямбда-выражения, вывод типов локальных переменных, анонимные типы и деревья выражений: все это языковые возможности. Вот почему существуют тонкие различия между VB.NET и C #: если бы эти функции были частью виртуальной машины, они были бы одинаковыми на всех языках. Но среда CLR не изменилась: в .NET 3.5 SP1 она по-прежнему такая же, как и в .NET 2.0. Вы можете скомпилировать программу на C #, использующую LINQ, с помощью компилятора .NET 3.5 и по-прежнему запускать ее в .NET 2.0, при условии, что вы не используете никаких библиотек .NET 3.5. Это не будет работать с дженериками и .NET 1.1, но это будет работать с Java и Java 1.4.

c# java generics