Чи слід позначати всі методи віртуальними?


74

У Java ви можете позначити метод як остаточний, щоб зробити неможливим перевизначення.

У C # ви повинні позначити метод як віртуальний, щоб зробити можливим перевизначення.

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


1
Мені подобається думати, що в C #, якщо ви робите щось перебірливе, ви робите це неправильно.
Louis Kottmann

4
@KonradRudolph, цитування / джерело, будь ласка!
bestsss

1
@bestsss Ні, я точно не маю. Остаточні поля абсолютно не пов’язані між собою.
Конрад Рудольф

1
@bestsss Забудь про finalполя, я знаю, що він про них говорить. Але я думаю, що він також говорить про остаточні методи в Effective Java . Однак я можу згадати це неправильно. Але я впевнений, що він згадував про це ще десь - можливо, в одній зі своїх доповідей.
Конрад Рудольф

Відповіді:


141

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

Ні. Якщо дизайнери мов вважали, що за замовчуванням мав бути віртуальний, тоді це був би за замовчуванням .

Надмірна бібліотечність - це особливість , і, як і всі функції, вона має свої витрати . Витрати на переоцінюваний метод значні: існують великі витрати на проектування, впровадження та тестування, особливо якщо існує якась "чутливість" до класу; віртуальні методи - це способи введення в систему неперевіреного стороннього коду, що впливає на безпеку.

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

Я виступаю за те, щоб усі класи були запечатані, а всі методи були невіртуальними, поки у вас не буде реальних причин, орієнтованих на клієнта, щоб розпечатати або зробити метод віртуальним.

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


17
+100 для "всі класи запечатати, а всі методи бути невіртуальними, поки у вас не буде реальних причин, орієнтованих на споживача, щоб розпечатати або зробити метод віртуальним".
Таллетс

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

6
@PopCatalin Ваше припущення полягає в тому, що створення більшої кількості віртуальних речей захищає вашого клієнта. На насправді, він піддає клієнт на ваші помилки більш .
деворде

27
-1 Я теж не згоден. Вам не доведеться обмежувати розширюваність програмного забезпечення всіма відомими випадками використання ваших класів. Запечатані + невіртуальні типи перешкоджають тестуванню та розширюваності ваших типів та будь-якого програмного забезпечення, яке використовує ваші типи. Ви можете подивитися на System.Web як на один із найбільш обтяжених і неперевірених HTTP-стеків, що використовуються сьогодні, 10 років тому, і ми все ще не позбавлені конкретних типів ASP.NET та його незмінних імпульсів. Після декількох спроб нових веб-абстракцій самі держави-члени не можуть ділитися функціональністю між різними стеками HTTP
mythz

12
Я вважаю, що, мабуть, дуже важливо провести різницю у відповідності: "відповідна" відповідь тут. "fwks для інших" та "код, який використовується ppl w для подальшої зміни джерела". Невіртуальний і запечатаний у .NET fwk (як приклад) використовувався там, де цього ніколи не мав бути, і споживачі цього fwk все ще сідлані з тими 10-річними "припущеннями" про "необхідні" точки розширюваності. Ви НЕ МОЖЕТЕ передбачити, як ppl захоче / потребує розширення ваших класів, тому, хоча 'virt-by-default' може бути занадто далеко в інший бік, запечатане за замовчуванням є не менш поганим вибором IMO.
sbohlen

28

На мою думку, прийнята в даний час відповідь є надмірно догматичною.

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

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

  1. Якщо у вас немає дуже вагомих причин, не позначайте класи як sealed.
  2. Якщо ваша бібліотека призначена для споживання іншими, принаймні спробуйте позначити основні методи класу, що містять поведінку, як virtual.

Один із способів здійснити виклик - переглянути назву методу або властивості. Метод GetLength () у списку робить саме те, що випливає з назви, і не дозволяє багато тлумачити . Зміна його реалізації, швидше за все, була б не надто прозорою, тому позначати це як virtualімовірно непотрібне. Позначення Addметоду як віртуального є набагато кориснішим, оскільки хтось може створити спеціальний список, який приймає лише деякі об'єкти за допомогою методу Add тощо. Іншим прикладом є спеціальні елементи керування. Ви хотіли б зробити основний метод малювання, virtualщоб інші могли використовувати основну масу поведінки та просто змінювати зовнішній вигляд, але ви, мабуть, не перевизначали властивості X та Y.

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


22

Ні! Тому що ви не знаєте , як буде успадковуватися ваш клас, ви повинні тільки позначити метод як virtualякщо ви знаєте , що ви хочете бути перевизначені.


4

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

Віртуальний не пов'язаний з фінальним.

Щоб запобігти перевизначенню віртуального методу в c #, який ви використовуєте sealed

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

5
FYI, на Java, finalза методом означає not virtual.
Джон Сондерс,

3

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

На Java є мільйони непередбачених неофіційних публічних методів, але ми чуємо дуже мало історій жахів.

У C # є мільйони запечатаних публічних методів, і ми чуємо дуже мало історій жахів.

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


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


2

Так ти повинен. Я хочу відповісти на іншу відповідь, ніж більшість інших відповідей. Це недолік C #. Дефект. Помилка в його оформленні. Ви можете бачити це при порівнянні з Java, де всі методи є "віртуальними", якщо не вказано інше ("остаточний"). Звичайно, якщо є клас "Прямокутник" із методом "Площа", і ви хочете мати власний клас, що представляє "Прямокутник" з полями. Ви хочете скористатися наявним класом з усіма його властивостями та методами, і вам просто потрібно додати властивість "margin", що додає деяке значення до звичайної області прямокутника, і якщо метод area у Rectangle не позначений як віртуальний, ти приречений. Зображте, будь ласка, метод, який бере масив прямокутників і повертає суму площі всіх прямокутників. Деякі можуть бути звичайними прямокутниками, а деякі з полями. Тепер прочитайте відповідь із позначкою "правильна", опишіть "безпечна проблема" або "тестування". Вони позбавлені сенсу порівняно з обмеженими можливостями.

Я не здивований, що інші відповіли "ні". Я здивований, що автори C # не змогли цього побачити, базуючи свою мову на Java.


0

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

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

Часто існують причини, не пов'язані з продуктивністю, для позначення класів як sealedабо обмеження успадкування іншими класами в складі. Крім усього іншого, якщо клас успадковується зовні, усі члени protectedобласті дії ефективно додаються до його загальнодоступного API, і будь-які зміни в їх поведінці в базовому класі можуть порушити будь-які похідні класи, які покладаються на цю поведінку. З іншого боку, якщо клас успадковується, створення його методів virtualнасправді не збільшує його вплив. У будь-якому випадку, це може зменшити залежність похідного класу від внутрішніх елементів базового класу, дозволяючи їм повністю "поховати" аспекти реалізації базового класу, які більше не актуальні в похідному класі [наприклад, якщо члениList<T>були віртуальними, похідний клас, який замінив їх усі, міг використовувати масив масивів для зберігання речей (уникаючи проблем з великою купою об’єктів), і не довелося б намагатися зберегти приватний масив, що використовується List<T>відповідно до масиву-of- масиви.


0

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.