Страницы

Поиск по вопросам

понедельник, 25 ноября 2019 г.

Недостатки обобщенных типов в C#


Хотелось бы задать такой вопрос: неоднократно слышал, что обобщенные типы (generics
в C# менее мощны, нежели шаблоны в C++. Но вот каких-либо доводов в пользу (или против) этого утверждения я не слышал. Действительно ли это так, если да, то в чем именно это  проявляется.

P.S. От себя могу лишь добавить, что недавно столкнулся с такой досадной и странной  особенностью, что если SomeClassChild — потомок класса SomeClass, то 
List нельзя преобразовать к List, тогда как SomeClassChild[] к SomeClass[] — можно. 

Более того, следующий код также приведет к ошибке: 

List lst = new List();
lst.Add(new SomeClassChild()); 

    


Ответы

Ответ 1



Разница на самом деле большая. Шаблоны C++ -- продвинутая версия макросов. В C# же генерики -- конструкция времени выполнения. Это определяет разницу. Например, такое: template T add(T lhs, T rhs) { return lhs + rhs; } проходит в C++: во время компиляции шаблона компилятор точно знает тип T и знает может ли к нему применяться оператор +. Аналогичная конструкция не сработает в C#: компилятор генерирует один код для всех на все случаи, и он не знает, что такое + для произвольных классов. Можно, конечно, обойти проверку при помощи такого трюка: T add(T lhs, T rhs) { dynamic l = lhs; dynamic r = rhs; return (T)(l + r); } -- но эта конструкция выявит отсутствие оператора + во время выполнения, а не компиляции. Из того, что генерики -- конструкция времени выполнения, выплывают и другие отличия В частности, в генериках, в отличие от шаблонов C++, нельзя сделать отдельную специализацию для конкретного типа параметров, или для некоторого их набора. (Для шаблонов это называется "частичная/явная специализация".) Теперь по поводу вашего примера: List lderived = new List(); List lbase = lderived; // не компилируется То, что приведённая строка не компилируется -- правильно. И не должно. Представьте себе, что этот код таки скомпилировался. Тогда вы сможете написать: lbase.Add(new SomeClass()); А это уже плохо, потому что теперь в lderived появился экземпляр чужого класса! То что SomeClassChild[] можно привести к SomeClass[] -- это "тяжёлое наследие" старых версий языка, когда не была реализована ковариантность генериков. (Вставить элемент чужого класса всё равно на получится, вылетит с исключением.) По поводу List lst = new List(); lst.Add(new SomeClassChild()); -- это должно работать. Перепроверьте! Следует отметить, что у генериков C# есть и преимущества по сравнению с C++. Например код генерика один и тот же для всех специализаций (на уровне IL, JIT-компилятор може ради оптимизации сделать несколько разных специализаций на уровне нативного кода). Поэтом вы можете компилировать код, использующий генерик, даже не имея полного исходного текста генерика. Для шаблонов C++ это невозможно: шаблон должен быть перекомпилирован для каждого из типов, с которым он используется. Эта особенность существенно замедляет компиляцию шаблонов в C++ по сравнению с C#, где генерик компилируется лишь один раз. Дополнение: поскольку в C++ все инстанциации шаблона должны быть известны во врем компиляции, это не позволяет динамически решать, с каким типом будет инстанциирован шаблон (что ограничивает семантику программ). У C# инстанциация шаблона происходит во время выполнения, и проблем не возникает. Пример: C#, C++.

Ответ 2



Шаблоны в Си++ компилируются под каждый конкретный аргумент в новый кусок кода. Эт позволяет использовать в них в полной мере все возможности того типа, который в них подставлен. Генерики в C# компилируются в код, который может быть применён для любого переданног в них типа (подходящего под объявленное условие). Из-за этого сразу теряются следующие возможности: Использование статических методов генерик-типа, а следовательно всех операторов (кром default property, конечно). Есдинственное исключение сделано для new без параметров. И то он вызывается рефлексией, поскольку другого способа в .net-рантайме нет. Возможность наследоваться от генерик-параметра. Возможность использовать какие-либо методы, наличие которых не гарантируется перечисленными в where ограничениями на классы и интерфейсы. Шаблоны в C# компилируются в код, подходящий под любой допустимый параметр. Т. е шаблон в шарпе - это просто функция - обычная функция, которая будет исполняться так как ей предписано, независимо от реального типа. Все вызовы строятся на основе объявленных ограничений на generic-тип. Реальный тип кикак не используется. Поэтому по-другому работают такие вещи как: Вызов невиртуальных методов и методов интерфейса. Вызывается метод базового класса, а не реального, как было бы в Си++. http://ideone.com/SfnNff Разрешение перегрузки функций происходит во время компиляции, когда сведения о конкретном типе неизвестны. http://ideone.com/DKCu9V Вроде было ещё что-то, но сейчас не могу вспомнить.

Ответ 3



Ещё одно преимущество шаблонов C++ заключается в том, что они позволяют параметризоват функцию или класс не только типом, но и значением. Ещё в последней версии C++ появились шаблоны с переменным числом параметров.

Комментариев нет:

Отправить комментарий