Должен ли я отмечать все методы виртуальными?

В Java вы можете пометить метод как final, чтобы сделать его невозможным.

В С# вы должны пометить метод как виртуальный, чтобы можно было переопределить.

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

Ответ 1

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

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

Overridablility - это функция, и, как и все функции, она имеет свои затраты. Стоимость переопределяемого метода значительна: существуют большие затраты на проектирование, внедрение и тестирование, особенно если есть какая-либо "чувствительность" к классу; виртуальные методы - это способы внедрения непроверенного стороннего кода в систему и оказывающие влияние на безопасность.

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

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

В основном ваш вопрос: "Я не знаю, как мои клиенты намерены использовать мой класс, поэтому я должен сделать его произвольно расширяемым?" Нет; вы должны стать узнаваемыми! Вы не спрашивали: "Я не знаю, как мои клиенты будут использовать мой класс, поэтому я должен использовать все свои свойства для чтения и записи? И должен ли я использовать все мои методы для чтения и записи свойств типа делегата, чтобы мои пользователи могут заменить любой метод своей собственной реализацией?" Нет, не делайте ни одной из этих вещей, пока не получите доказательства того, что пользователь действительно нуждается в этой возможности! Потратьте свое драгоценное время на проектирование, тестирование и реализацию функций, которые действительно нужны и нужны пользователям, и делать это с позиции знаний.

Ответ 2

По-моему, принятый в настоящее время ответ является излишне догматичным.

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

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

  • Если у вас нет веской причины, не помещайте классы как sealed.
  • Если ваша библиотека предназначена для потребления другими, попробуйте пометить основные методы класса, которые содержат поведение virtual.

Один из способов сделать вызов - посмотреть имя метода или свойства. Метод GetLength() в List делает именно то, что подразумевается в названии, и это не допускает значительной интерпретации. Изменение его реализации, вероятно, будет не очень прозрачным, поэтому обозначить его как virtual, вероятно, не нужно. Маркировка метода Add как виртуального гораздо полезнее, поскольку кто-то может создать специальный Список, который принимает только некоторые объекты с помощью метода Add и т.д. Другим примером являются пользовательские элементы управления. Вы хотели бы сделать основной метод рисования virtual, чтобы другие могли использовать основную часть поведения и просто изменять внешний вид, но вы, вероятно, не переопределите свойства X и Y.

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

Ответ 3

Нет! Поскольку вы не знаете, как ваш класс будет наследоваться, вы должны отмечать только метод virtual, если вы знаете, что хотите его переопределить.

Ответ 4

Нет. Только методы, которые вы хотите, чтобы производные классы указывали, должны быть виртуальными.

Виртуальный не связан с окончательным.

Чтобы предотвратить переопределение виртуального метода в С#, вы используете sealed

public class MyClass
{
    public sealed override void MyFinalMethod() {...}
}

Ответ 5

Мы можем вызвать причины для/снова в лагере, но это совершенно бесполезно.

В Java есть миллионы непреднамеренных незавершенных публичных методов, но мы слышим очень мало ужасных историй.

В С# есть миллионы закрытых общедоступных методов, и мы слышим очень мало ужасных историй.

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


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

Ответ 6

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

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

Часто существуют причины, связанные с несоблюдением классов для маркировки как sealed или ограничение наследования для других классов в сборке. Среди прочего, если класс внешне наследуется, все члены с областью protected эффективно добавляются в свой общедоступный API, и любые изменения их поведения в базовом классе могут нарушать любые производные классы, которые полагаются на это поведение. С другой стороны, если класс наследуется, то его методы virtual на самом деле не увеличивают его воздействие. Во всяком случае, это может снизить зависимость производного класса от внутренних компонентов базового класса, разрешив им полностью "похоронить" аспекты реализации базового класса, которые больше не имеют отношения к производному классу [например, если члены List<T> были виртуальными, производный класс, который их перегружал, мог использовать массив массивов для хранения вещей (избегая проблем с большими объектно-кучами), и не пришлось бы пытаться сохранить частный массив, используемый List<T> в соответствии с массивом массивов.

Ответ 7

Нет, вы не должны отмечать все методы как виртуальные. Вы должны подумать о том, как ваш класс может быть унаследован. Если класс не должен быть унаследован, то отметьте его запечатанным и, очевидно, члены не должны быть виртуальными. Если ваш класс, скорее всего, наследуется, вы действительно должны максимизировать способность переопределять поведение. Поэтому щедро используйте виртуальные везде в таких классах, если у вас нет причин.

Ответ 8

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

Иногда кто-то дальше по линии хочет изменить, как работает Length().

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

Я полагаю, что большинство людей, отвечающих здесь, скажут, что оригинальный автор не должен был использовать С# вообще, если они намеревались разрешить моддинг?

Так. Подумайте, насколько полезно, чтобы ваш код был другим, а затем принимайте решение.