Страницы

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

понедельник, 1 октября 2018 г.

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

Хотелось бы задать такой вопрос: неоднократно слышал, что обобщенные типы (generics) в C# менее мощны, нежели шаблоны в C++. Но вот каких-либо доводов в пользу (или против) этого утверждения я не слышал. Действительно ли это так, если да, то в чем именно это проявляется. P.S. От себя могу лишь добавить, что недавно столкнулся с такой досадной и странной особенностью, что если SomeClassChild - потомок класса SomeClass, то List нельзя преобразовать к List, тогда как SomeClassChild[] к SomeClass[] - можно. Более того, следующий код также приведет к ошибке: List lst = new List(); lst.Add(new SomeClassChild());


Ответ

Разница на самом деле большая. Шаблоны 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++

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

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