Ответ 1
С# был спроектирован как слегка более гибкий язык, чем С++.
В частности, я не думаю, что отличная идея сравнить С# generics с шаблонами С++ по разным причинам - они принципиально два очень разных подхода к решению подобных вещей в некоторых ситуациях. Подход С++, безусловно, является гибким в некоторых отношениях - хотя он не позволяет (как я понимаю) шаблоны, которые существуют только в двоичной форме, или создавать новые шаблоны во время выполнения. В принципе, подход к шаблонам С++ не подходит для остальных, как .NET подходит друг к другу.
Теперь о том, почему вы не можете указать некоторые аргументы типа и позволить другим быть выведенным (это решение языка, а не решение платформы, я уверен, что это будет возможно, насколько это касается самой .NET) - опять же, я считаю, что это ради простоты. Выбор точного правильного метода и правильных аргументов типа уже очень сложный в С# - более сложный, чем большинство разработчиков С# могут обходить головы. Он включает в себя:
- Потенциально рассматривая методы для иерархии типов из типа времени компиляции целевого
- Перегрузка по количеству параметров
- Перегрузка по количеству параметров типа
- Эффект именованных аргументов
- Влияние дополнительных параметров
- Влияние ограничений типа типового типа на типы параметров (не ограничения, заданные целевым методом, примечание)
- Группа методов для делегирования конверсий
- Анонимные преобразования функций
- Вывод типа для аргументов типа
- Динамическая типизация
- Общая ковариация и контравариантность
Лично я считаю, что достаточно, чтобы окунуться в голову, не допуская еще больше возможностей с помощью "M все еще может быть кандидатом, если у него есть как минимум столько же параметров типа, сколько указано в аргументах типа". Вы также хотите, чтобы именованные типы аргументов и необязательные параметры типа?;)
Я смотрел на перегрузку довольно много, тщательно следуя спецификации и т.д. Я нашел области, которые заставляют дизайнеров языка почесывать головы и пытаться выяснить, что должен делать компилятор. Я нашел области, которые компилятор определенно ошибается. Я бы не хотел добавлять здесь больше сложностей без по-настоящему веской причины.
Итак, да, это в основном ради простоты, а иногда и боли - но часто вы можете обойти это. Для каждой потенциальной функции вам необходимо учитывать:
- Преимущество функции для разработчиков
- Стоимость функции для конечных разработчиков с точки зрения времени, потраченного на ее понимание
- Стоимость разработчикам языка при проектировании и конкретизации.
- Стоимость для авторов компилятора при его правильном использовании
- Стоимость тестовой команды при ее тщательном тестировании (в сочетании со всем остальным при перегрузке)
- Стоимость будущих потенциальных возможностей (если это делает язык более сложным, что оставляет меньше "потенциально громоздкой" дополнительной сложности для других функций)