Хотелось бы задать такой вопрос: неоднократно слышал, что обобщенные типы (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++
Комментариев нет:
Отправить комментарий