Чому C # реалізує методи як невіртуальні за замовчуванням?


105

На відміну від Java, чому C # розглядає методи як не віртуальні функції за замовчуванням? Чи швидше це питання ефективності, а не інших можливих результатів?

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


1
Відповіді, в яких згадуються причини продуктивності, ігнорують той факт, що компілятор C # здебільшого компілює виклики методів callvirt, а не виклик . Ось чому в C # неможливо мати метод, який поводиться інакше, якщо thisпосилання є нульовим. Дивіться тут для отримання додаткової інформації.
Енді

Правда! Інструкція виклику IL в основному стосується дзвінків, зроблених статичним методам.
RBT

1
Думки архітектора C # Anders Hejlsberg тут і тут .
RBT

Відповіді:


98

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

virtualметоди також можуть мати незначний вплив на продуктивність. Це, мабуть, не є основною причиною.


7
Особисто я сумніваюся в тій частині щодо продуктивності. Навіть для віртуальних функцій компілятор дуже здатний розібратися, де його замінити callvirtпростим callу коді IL (або навіть далі за течією при JITting). Java HotSpot робить те саме. Решта відповіді - точкова.
Конрад Рудольф

5
Я погоджуюся, що ефективність не на перший план, але це може мати і наслідки для продуктивності. Це, мабуть, дуже мало. Звичайно, це не причина цього вибору. Просто хотів це згадати.
Мехрдад Афшарі

71
Запечатані заняття змушують мене пробивати дітей.
mxmissle

6
@mxmissle: мені теж, але хороший дизайн API все одно важкий. Я думаю, що зашифрований за замовчуванням має ідеальний сенс для вашого коду. Частіше інший програміст у вашій команді не вважав спадщину, коли реалізував потрібний вам клас, і запечатаний за замовчуванням передасть це повідомлення досить добре.
Роман Старков

49
Багато людей теж є психопатами, це не означає, що ми повинні їх слухати. У C # віртуальна версія повинна бути типовою, код повинен бути розширюваним без необхідності змінювати реалізацію або повністю переписувати її. Люди, які надмірно захищають свої API, часто стикаються з мертвими або напівзастосованими API, що набагато гірше, ніж сценарій того, що хтось його зловживає.
Кріс Нікола

89

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

Більшість виправдань мені читають, як старий аргумент "Якщо ми дамо тобі владу, ти можеш нашкодити собі". Від програмістів ?!

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

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

Невіртуальний за замовчуванням ускладнює моє життя.

ОНОВЛЕННЯ: [цілком правильно] було зазначено, що я фактично не відповів на питання. Отож - і з вибаченнями за те, що досить пізно….

Мені хотілося, щоб можна було написати щось пітне, наприклад, "C # реалізує методи як невіртуальні за замовчуванням, тому що було прийнято неправильне рішення, яке оцінювало програми більш високо, ніж програмісти". (Я думаю, що це може бути дещо виправданим, грунтуючись на деяких інших відповідях на це запитання - на зразок продуктивності (передчасна оптимізація, хтось?) Чи гарантування поведінки класів.)

Однак я усвідомлюю, що просто висловлюю свою думку, а не ту остаточну відповідь, яку бажає Stack Overflow. Звичайно, я подумав, що на найвищому рівні остаточна (але непомітна) відповідь така:

За замовчуванням вони не віртуальні, тому що дизайнери мови мали прийняти рішення, і саме це вони обрали.

Зараз я здогадуюсь про точну причину того, що вони прийняли таке рішення, ми ніколи .... о, чекайте! Стенограма розмови!

Отже, здавалося б, що відповіді та коментарі щодо небезпеки переважаючих API та необхідності чітко розробити спадщину на правильному шляху, але у всіх пропущений важливий часовий аспект: головна турбота Андерса полягала у підтримці неявного класу чи API контракт у різних версіях . І я думаю, що насправді він більше стурбований тим, щоб дозволити зміну платформи .Net / C # під кодом, а не стурбований тим, щоб змінити код користувача на вершині платформи. (І його "прагматична" точка зору - це якраз протилежний моєму, тому що він дивиться з іншого боку.)

(Але хіба вони не могли просто вибрати віртуальні за замовчуванням, а потім переглянули "остаточний" через кодову базу? Можливо, це не зовсім те саме. А Андер явно розумніший за мене, тому я збираюся дати йому лежати.)


2
Не можу не погодитись. Дуже засмучує, коли споживаєш третій сторонній апі і хочеш перемогти якусь поведінку, а не можеш.
Енді

3
Я із захопленням погоджуюся. Якщо ви збираєтесь публікувати API (будь то в компанії чи у зовнішньому світі), ви дійсно можете і повинні переконатися, що ваш код призначений для спадкування. Ви повинні зробити API хорошим і відшліфованим, якщо ви публікуєте його, щоб його використовували багато людей. У порівнянні із зусиллями, необхідними для гарного загального дизайну (хороший вміст, чіткі випадки використання, тестування, документація), проектування для успадкування насправді не надто погано. І якщо ви не публікуєте, за замовчуванням віртуальний забирає менше часу, і ви завжди можете виправити невелику частину випадків, коли це проблематично.
Гравітація

2
Тепер, якщо в Visual Studio була лише функція редактора, щоб автоматично позначати всі методи / властивості як virtual... Додаток Visual Studio хто?
kevinarpe

1
Хоча я щиро погоджуюся з вами, це не зовсім відповідь.
Кріс Морган

2
З огляду на сучасні практики розвитку, такі як ін'єкція залежності, знущання з фреймворків та ORM, здається зрозумілим, що наші C # дизайнери трохи пропустили позначку. Дуже неприємно перевіряти залежність, коли ти не можеш замінити властивість за замовчуванням.
Джеремі Головач

17

Тому що занадто просто забувати, що метод може бути переосмислений і не призначений для цього. C # змушує задуматися, перш ніж зробити це віртуальним. Я думаю, що це чудове дизайнерське рішення. Деякі люди (наприклад, Джон Скіт) навіть казали, що класи повинні бути запечатані за замовчуванням.


12

Підсумовуючи сказане іншими, є декілька причин:

1- У C # є багато речей у синтаксисі та семантиці, які походять прямо з C ++. Справа в тому, що методи, де не віртуальні за замовчуванням у C ++, впливали на C #.

2- Наявність кожного методу віртуального за замовчуванням - це питання продуктивності, оскільки кожен виклик методу повинен використовувати Віртуальну таблицю об'єкта. Більше того, це сильно обмежує можливість компілятора Just-In-Time вбудовувати методи та виконувати інші види оптимізації.

3- Найголовніше, якщо методи за замовчуванням не віртуальні, ви можете гарантувати поведінку своїх класів. Якщо вони за замовчуванням є віртуальними, як, наприклад, у Java, ви навіть не можете гарантувати, що простий метод getter виконуватиметься за призначенням, оскільки його можна буде перекрити, щоб зробити що-небудь у похідному класі (звичайно, ви можете, і потрібно, робити метод та / або підсумковий клас).

Як можна було б зауважити, як згадував Зіфре, чому мова C # не пішла ні на крок далі, і зробити класи запечатаними за замовчуванням. Це частина всієї дискусії щодо проблем успадкування впровадження, що є дуже цікавою темою.


1
Я б хотів, щоб класи також були запечатані за замовчуванням. Тоді це було б послідовно.
Енді

9

На C # впливає C ++ (і більше). C ++ не дає змоги динамічно відправляти (віртуальні функції) за замовчуванням. Одним (хорошим?) Аргументом для цього є питання: "Як часто ви реалізуєте класи, які є членами класу hiearchy?". Ще одна причина уникнення включення динамічної відправки за замовчуванням - це пам'ять. Клас без віртуального вказівника (vpointer), що вказує на віртуальну таблицю , звичайно менше, ніж відповідний клас із увімкненою пізньою прив'язкою.

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

Ще одне, схоже питання про " швидкість віртуальних дзвінків .. "


Я трохи сумніваюся, що віртуальні методи мають наслідки для продуктивності для C # через компілятора JIT. Це одна з областей, де JIT може бути кращим, ніж компіляція в режимі офлайн, оскільки вони можуть вбудовувати функціональні дзвінки, які є "невідомими" до часу виконання
Девід Курно

1
Власне, я думаю, що на неї більше впливає Java, ніж C ++, що робить це за замовчуванням.
Мехрдад Афшарі

5

Проста причина - це вартість проектування та обслуговування, крім витрат на експлуатацію. Віртуальний метод має додаткову вартість порівняно з невіртуальним методом, оскільки дизайнер класу повинен планувати, що відбувається, коли метод буде замінений іншим класом. Це має великий вплив, якщо ви очікуєте, що певний метод оновить внутрішній стан або має певну поведінку. Тепер ви повинні планувати, що відбувається, коли похідний клас змінює цю поведінку. Набагато складніше написати надійний код у цій ситуації.

За допомогою невіртуального методу ви маєте тотальний контроль. Все, що піде не так - це вина оригіналу автора. Міркувати про код набагато простіше.


Це дійсно старий пост, але для новачка, який пише свій перший проект, я постійно переживаю за ненавмисні / невідомі наслідки написаного кодом. Це дуже втішно, знаючи, що невіртуальні методи є абсолютно МОЄЮ виною.
trevorc

2

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

Об'єкти C # мають віртуальні методи, лише якщо у класі визначені віртуальні методи. Це правда, що всі об'єкти мають інформацію про тип, яка включає еквівалент vtbl, але якщо не визначені віртуальні методи, тоді будуть присутні лише базові методи об'єкта.

@Tom Hawtin: Напевно, точніше сказати, що C ++, C # і Java - це сімейство мов C :)


1
Чому для vtable було б набагато більше? В класі є лише 1 vtable (а не за примірник), тому його розмір не робить суттєвого значення.
Гравітація

1

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

Розглянемо клас списку «Додати метод. Що робити, якщо розробник захотів оновити одну з декількох потенційних баз даних кожного разу, коли певний список буде доданий до? Якщо "Додати" за замовчуванням було віртуальним, розробник міг би розробити клас "BackedList", який замінив метод "Add", не примушуючи весь код клієнта знати, що це "BackedList" замість звичайного "Список". У всіх практичних цілях "BackedList" може розглядатися як лише інший "Список" з коду клієнта.

Це має сенс з точки зору великого основного класу, який може забезпечити доступ до одного або декількох компонентів списку, які самі підтримуються однією або кількома схемами в базі даних. Зважаючи на те, що методи C # за замовчуванням не є віртуальними, список, наданий основним класом, не може бути простим IEnumerable чи ICollection або навіть екземпляром List, а натомість повинен бути оголошений клієнтові як "BackedList", щоб гарантувати нову версію операції "Додати" викликається для оновлення правильної схеми.


Правда, віртуальні методи за замовчуванням полегшують роботу, але чи є сенс? Ви можете скористатися багатьма речами, щоб полегшити життя. Чому б не просто громадські поля на заняттях? Змінити свою поведінку занадто просто. На мою думку, все на мові повинно бути суворо укладене, жорстке та стійке до змін за замовчуванням. Змініть його лише за потреби. Просто будучи філософськими цілісними дизайнерськими рішеннями. Продовження ..
nawfal

... Для того, щоб говорити по поточній темі, спадкова модель повинна використовуватися тільки якщо Bє A. Якщо Bпотрібно щось інше, Aніж його, ні A. Я вважаю, що перевага, як здатність мови, є вадою дизайну. Якщо вам потрібен інший Addметод, то ваш клас колекції не є List. Спроба сказати це підробка. Правильний підхід тут - композиція (а не підробка). Правда, вся рамка побудована на переважних можливостях, але мені це просто не подобається.
nawfal

Я думаю, я розумію вашу думку, але на конкретному прикладі: "BackedList" може просто реалізувати інтерфейс "IList", і клієнт знає лише про інтерфейс. правильно? я щось пропускаю? проте я розумію більш широку точку, яку ви намагаєтеся зробити.
Ветрас

0

Це, звичайно, не питання про продуктивність. Інтерпретатор Java Sun використовує той самий код для відправки ( invokevirtualбайт-код), а HotSpot генерує абсолютно той самий код, чи finalні. Я вважаю, що всі об'єкти C # (але не структури) мають віртуальні методи, тому вам завжди буде потрібно vtblідентифікація класу / час виконання. C # - діалект "Явоподібних мов". Припустити, що це походить від C ++, не зовсім чесно.

Існує думка, що вам слід «спроектувати на спадщину або ще заборонити це». Це звучить як чудова ідея аж до того моменту, коли у вас є серйозний діловий випадок, який потрібно швидко вирішити. Можливо, успадковане від коду, який ви не контролюєте.


Хотспот змушений намагатися зробити оптимізацію саме тому, що всі методи за замовчуванням є віртуальними, що має величезний вплив на продуктивність. CoreCLR здатний домогтися аналогічних показників, при цьому набагато простіше
Yair Halberstadt

@YairHalberstadt Hotspot потрібно мати можливість резервного копіювання складеного коду з різних інших причин. Минуло роки, як я подивився на джерело, але різниця між finalта ефективними finalметодами банальна. Варто також зазначити, що він може виконувати біморфне вбудовування, тобто вбудовані методи з двома різними реалізаціями.
Том Хотін - таклін
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.