Почему С# реализует методы как не виртуальные по умолчанию?

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

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

Ответ 1

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

Методы

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

Ответ 2

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

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

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

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

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

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

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

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

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

Теперь я думаю, что именно потому, что они приняли это решение, мы никогда не... о, подождите! Расшифровка беседы

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

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

Ответ 3

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

Ответ 4

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

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

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

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

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

Ответ 5

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

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

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

Ответ 6

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

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

Ответ 7

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

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

@Tom Hawtin: Вероятно, более точно сказать, что С++, С# и Java - все из семейства языков C:)

Ответ 8

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

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

Ответ 9

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

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

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