Почему C # реализует методы как не виртуальные по умолчанию? - PullRequest
105 голосов
/ 02 мая 2009

В отличие от Java, почему в C # методы по умолчанию рассматриваются как не виртуальные функции? Это скорее проблема производительности, чем другие возможные результаты?

Мне напоминают о прочтении параграфа Андерса Хейлсберга о некоторых преимуществах, которые дает существующая архитектура Но как насчет побочных эффектов? Это действительно хороший компромисс, если по умолчанию использовать не виртуальные методы?

Ответы [ 9 ]

98 голосов
/ 02 мая 2009

Классы должны быть предназначены для наследования, чтобы иметь возможность использовать его в своих интересах. Наличие методов virtual по умолчанию означает, что каждая функция в классе может быть отключена и заменена другой, что не очень хорошая вещь. Многие даже считают, что классы должны были быть sealed по умолчанию.

Методы

virtual также могут иметь незначительное влияние на производительность. Однако вряд ли это будет основной причиной.

87 голосов
/ 18 июня 2010

Я удивлен, что здесь, кажется, существует такой консенсус, что не виртуальный по умолчанию является правильным способом сделать что-то. Я собираюсь спуститься на другую - я думаю, прагматическую - сторону забора.

Большинство оправданий читаются мне как старый аргумент «Если мы дадим вам силу, вы можете навредить себе». От программистов?!

Мне кажется, что кодер, который не знал достаточно (или не имел достаточно времени), чтобы спроектировать свою библиотеку для наследования и / или расширяемости, - это кодер, который создал именно ту библиотеку, которую мне, скорее всего, придется исправить или настроить - именно та библиотека, в которой возможность переопределения будет наиболее полезной.

Сколько раз мне приходилось писать уродливый, отчаянный обходной код (или отказываться от использования и накатывать свое собственное альтернативное решение), потому что я не могу переопределить слишком далеко, намного превышает количество раз, которые я когда-либо был укушен (например, в Java) переопределением там, где дизайнер, возможно, не учел, я мог бы.

Не виртуальный по умолчанию делает мою жизнь сложнее.

ОБНОВЛЕНИЕ: Было указано [совершенно правильно], что я на самом деле не ответил на вопрос. Так что - и с извинениями за опоздание ....

Я хотел бы написать что-то наподобие «C # реализует методы как не виртуальные по умолчанию, потому что было принято неверное решение, которое оценивает программы выше, чем программисты». (Я думаю, что это может быть несколько оправдано, основываясь на некоторых других ответах на этот вопрос, таких как производительность (преждевременная оптимизация, кто-нибудь?) Или гарантия поведения классов.)

Однако я понимаю, что просто излагаю свое мнение, а не тот окончательный ответ, который желает переполнение стека. Конечно, я думал, что на самом высоком уровне окончательный (но бесполезный) ответ:

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

Теперь, я думаю, точную причину, по которой они приняли это решение, мы никогда ... о, подождите! Стенограмма разговора!

Таким образом, кажется, что ответы и комментарии здесь об опасностях переопределения API и необходимости явного проектирования наследования находятся на правильном пути, но все они упускают важный временный аспект: главная задача Андерса заключалась в поддержании класса или неявный контракт API между версиями . И я думаю, что он на самом деле больше заботится о том, чтобы позволить платформе .Net / C # меняться под кодом, а не беспокоится об изменении кода пользователя поверх платформы. (А его «прагматичная» точка зрения является полной противоположностью моей, потому что он смотрит с другой стороны.)

(Но разве они не могли просто выбрать виртуальный по умолчанию, а затем перенаправить «финал» через кодовую базу? Возможно, это не совсем то же самое ... и Андерс явно умнее меня, поэтому я позволю этому ложь.)

17 голосов
/ 02 мая 2009

Потому что слишком легко забыть, что метод может быть переопределен и не предназначен для этого. C # заставляет вас думать, прежде чем сделать его виртуальным. Я думаю, что это отличное дизайнерское решение. Некоторые люди (например, Джон Скит) даже говорят, что классы должны быть запечатаны по умолчанию.

12 голосов
/ 02 мая 2009

Подводя итог тому, что говорили другие, есть несколько причин:

1- В C # есть много вещей в синтаксисе и семантике, которые приходят прямо из C ++. Тот факт, что методы, не являющиеся виртуальными по умолчанию в C ++, повлияли на C #.

2- Наличие каждого виртуального метода по умолчанию является проблемой производительности, потому что каждый вызов метода должен использовать виртуальную таблицу объекта. Кроме того, это сильно ограничивает способность компилятора Just-In-Time встроять методы и выполнять другие виды оптимизации.

3- Самое главное, если методы не являются виртуальными по умолчанию, вы можете гарантировать поведение ваших классов. Когда они являются виртуальными по умолчанию, например в Java, вы даже не можете гарантировать, что простой метод получения будет работать так, как задумано, потому что он может быть переопределен, чтобы делать что-либо в производном классе (конечно, вы можете и должны сделать метод и / или класс final).

Можно было бы задаться вопросом, как упоминал Зифре, почему язык C # не пошел дальше и не сделал классы запечатанными по умолчанию. Это часть всей дискуссии о проблемах наследования реализации, которая является очень интересной темой.

9 голосов
/ 02 мая 2009

C # зависит от C ++ (и более). C ++ не включает динамическую диспетчеризацию (виртуальные функции) по умолчанию. Одним из (хороших?) Аргументов для этого является вопрос: «Как часто вы реализуете классы, являющиеся членами класса hiearchy?». Другая причина, по которой следует избегать динамической отправки по умолчанию, - это объем памяти. Класс без виртуального указателя (vpointer), указывающего на виртуальную таблицу , конечно, меньше соответствующего класса с включенным поздним связыванием.

Проблема с производительностью не так просто сказать "да" или "нет". Причиной этого является компиляция Just In Time (JIT), которая является оптимизацией времени выполнения в C #.

Другой, похожий вопрос о " скорости виртуальных звонков .. "

5 голосов
/ 02 мая 2009

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

С не-виртуальным методом вы имеете полный контроль. Все, что идет не так, по вине первоначального автора. Код гораздо проще рассуждать.

2 голосов
/ 03 мая 2009

Если бы все методы C # были виртуальными, тогда vtbl был бы намного больше.

Объекты C # имеют только виртуальные методы, если в классе определены виртуальные методы. Это правда, что все объекты имеют информацию о типе, которая включает vtbl-эквивалент, но если виртуальные методы не определены, то будут присутствовать только методы базового объекта.

@ Том Хотин: Вероятнее, точнее сказать, что C ++, C # и Java все из семейства языков C:)

1 голос
/ 29 октября 2010

Исходя из опыта Perl, я думаю, что C # закрыл гибель каждого разработчика, который, возможно, хотел расширить и изменить поведение базового класса через не виртуальный метод, не заставляя всех пользователей нового класса быть осведомленными о потенциально за кулисами детали.

Рассмотрим метод List класса Add. Что, если разработчик захотел обновить одну из нескольких потенциальных баз данных всякий раз, когда конкретный список «добавлен»? Если бы «Добавить» было виртуальным по умолчанию, разработчик мог бы разработать класс «BackedList», который бы переопределил метод «Add», не заставляя весь клиентский код знать, что это «BackedList» вместо обычного «List». Для всех практических целей «BackedList» можно рассматривать как просто «List» из клиентского кода.

Это имеет смысл с точки зрения большого основного класса, который может обеспечить доступ к одному или нескольким компонентам списка, которые сами поддерживаются одной или несколькими схемами в базе данных. Учитывая, что методы C # не являются виртуальными по умолчанию, список, предоставленный основным классом, не может быть простым IEnumerable или ICollection или даже экземпляром List, но вместо этого должен быть объявлен клиенту как «BackedList», чтобы гарантировать, что новая версия операции «Добавить» вызывается для обновления правильной схемы.

0 голосов
/ 02 мая 2009

Это, конечно, не проблема производительности. Java-интерпретатор Sun использует один и тот же код для отправки (invokevirtual байт-код), а HotSpot генерирует точно такой же код независимо от того, final или нет. Я полагаю, что все объекты C # (но не структуры) имеют виртуальные методы, поэтому вам всегда потребуется идентификатор класса vtbl / runtime. C # - это диалект "Java-подобных языков". Предполагать, что это происходит из C ++, не совсем честно.

Существует идея, что вы должны «создать наследство или запретить его». Это звучит как отличная идея, пока у вас есть серьезное экономическое обоснование, которое нужно быстро исправить. Возможно наследование от кода, который вы не контролируете.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...