2009-05-02 4 views
97

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

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

+1

Ответы, в которых упоминаются соображения производительности, игнорируют тот факт, что компилятор C# в основном компилирует вызовы методов на * callvirt *, а не на * call *. Именно поэтому в C# невозможно иметь метод, который ведет себя по-другому, если ссылка 'this' равна null. См. [Здесь] (http://www.pvle.be/2008/11/extension-methods-and-null-objects/) для получения дополнительной информации. – Andy

+0

Правда! * call * IL-инструкция в основном предназначена для вызовов статических методов. – RBT

+0

Мысли автора C# Андерса Хейлсберга [здесь] (http://www.artima.com/intv/nonvirtualP.html) и [здесь] (http://stackoverflow.com/questions/973240/why-are-methods-virtual -по-умолчанию-в-Java-но-не-виртуальных по-умолчанию в-с). – RBT

ответ

92

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

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

+6

Лично я сомневаюсь, что часть о производительности. Даже для виртуальных функций компилятор может определить, где заменить «callvirt» простым «вызовом» в коде IL (или даже дальше вниз по течению, когда JITting).Java HotSpot делает то же самое. Остальная часть ответа находится на месте. –

+4

Я согласен, что производительность не стоит на переднем крае, но это может также иметь последствия для производительности. Это, вероятно, очень мало. Конечно, это не * причина * для этого выбора. Просто хотел сказать об этом. –

+63

Запечатанные классы заставляют меня хотеть ударить детей. – mxmissile

9

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

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

Другой подобный вопрос о «speed of virtual calls..»

+0

Я немного сомневаюсь, что виртуальные методы имеют последствия для производительности для C# из-за компилятора JIT. Это одна из областей, где JIT может быть лучше, чем автономная компиляция, потому что они могут встроить вызовы функций, которые являются «неизвестными» до выполнения. –

+0

Я согласен с вашей точкой зрения Roger! +1 – Geo

+1

На самом деле, я думаю, что это больше влияет на Java, чем на C++, что делает это по умолчанию. –

15

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

10

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

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

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

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

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

+0

Я бы предпочел классы, запечатанные по умолчанию тоже. Тогда это будет непротиворечиво. – Andy

5

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

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

+0

Это действительно старый пост, но для новичка, который пишет свой первый проект, я постоянно беспокоюсь о непреднамеренных/неизвестных последствиях кода, который я пишу. Это очень утешительно, зная, что не виртуальные методы - это полностью МОЯ ошибка. – trevorc

1

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

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

2

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

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

@tom Хотин: Это, вероятно, правильнее сказать, что C++, C# и Java являются все из семейства C языков :)

+0

Почему это будет проблемой для того, чтобы vtable был намного больше? В классе (а не на каждом экземпляре) есть только 1 vtable, поэтому его размер не имеет большого значения. – Gravity

80

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

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

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

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

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

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

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

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

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

Теперь я думаю, что именно потому, что они приняли это решение, мы никогда не ... ой, подождите! The transcript of a conversation!

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

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

+2

Не могу договориться подробнее. Очень неприятно, когда вы потребляете стороннюю api и хотите переопределить какое-то поведение и не сможете. – Andy

+3

Я с энтузиазмом соглашаюсь. Если вы собираетесь публиковать API (как внутри компании, так и во внешнем мире), вы действительно можете и должны убедиться, что ваш код предназначен для наследования. Вы должны сделать API хорошим и отполированным, если вы публикуете его, чтобы его использовали многие люди. По сравнению с усилиями, необходимыми для хорошего общего дизайна (хороший контент, четкие варианты использования, тестирование, документация), проектирование для наследования действительно не так уж плохо. И если вы не публикуете, виртуальные по умолчанию менее трудоемки, и вы всегда можете исправить небольшую часть случаев, когда это проблематично. – Gravity

+2

Теперь, если в Visual Studio появилась только функция редактора, которая автоматически маркирует все методы/свойства как «виртуальные» ... кто-нибудь добавляет Visual Studio? – kevinarpe

1

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

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

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

+0

Правда, виртуальные методы по умолчанию упрощают работу, но имеет ли смысл? Есть много вещей, которые вы могли бы использовать, чтобы облегчить жизнь. Почему не просто публичные поля в классах? Изменение его поведения слишком просто. По-моему, все на языке должно быть строго инкапсулировано, жестко и устойчиво к изменению по умолчанию. Измените его только в случае необходимости. Просто принятие философских решений. Продолжение .. – nawfal

+0

... Чтобы поговорить по текущей теме, модель наследования должна использоваться только в том случае, если 'B' является' A'. Если 'B' требует чего-то иного, чем' A', то это не 'A'. Я считаю, что переоценка как способность на самом языке является недостатком дизайна. Если вам нужен другой метод «Добавить», то ваш класс коллекции не является «списком». Попытка сказать, что это притворство. Правильный подход здесь - это композиция (а не фальсификация). Правда, вся инфраструктура построена на переопределяющих возможностях, но мне это просто не нравится. – nawfal

 Смежные вопросы

  • Нет связанных вопросов^_^