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


81

Я переглядав цю публікацію в блозі і мав такі запитання:

  • Навіщо нам newключове слово, це лише для того, щоб вказати, що метод базового класу приховується. Я маю на увазі, навіщо це нам потрібно? Якщо ми не використовуємо overrideключове слово, чи не приховуємо ми метод базового класу?
  • Чому за замовчуванням у C # приховувати та не перевизначати? Чому дизайнери реалізували це саме так?

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

Відповіді:


127

Гарні запитання. Дозвольте їх повторно заявити.

Чому взагалі законно приховувати метод за допомогою іншого методу?

Дозвольте відповісти на це запитання на прикладі. У вас є інтерфейс від CLR v1:

interface IEnumerable
{
    IEnumerator GetEnumerator();
}

Супер Зараз у CLR v2 у вас є загальні засоби, і ви думаєте: "чоловіче, якби у нас були дженерики у версії 1, я б зробив це загальним інтерфейсом. Але я цього не зробив. Зараз я повинен зробити із цим щось сумісне, що є загальним, щоб Я отримую переваги дженериків, не втрачаючи зворотної сумісності з кодом, який очікує IEnumerable. "

interface IEnumerable<T> : IEnumerable
{
    IEnumerator<T> .... uh oh

Що ви збираєтеся називати методом GetEnumerator IEnumerable<T>? Пам'ятайте, ви хочете, щоб він приховував GetEnumerator на не-загальному базовому інтерфейсі. Ви ніколи не хочете, щоб цю річ називали, якщо ви явно не знаходитесь у зворотній ситуації.

Це лише виправдовує приховування методів. Докладніше про виправдання методу приховування див. У моїй статті на цю тему .

Чому приховування без "нового" викликає попередження?

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

Чому приховування без "нового" попередження, а не помилки?

Та сама причина. Можливо, ви щось приховуєте випадково, бо щойно вибрали нову версію базового класу. Це відбувається постійно. FooCorp робить базовий клас B. BarCorp робить похідний клас D із методом Bar, оскільки їх споживачам подобається. FooCorp це бачить і каже, привіт, це гарна ідея, ми можемо покласти цю функціональність на базовий клас. Вони роблять це і постачають нову версію Foo.DLL, і коли BarCorp бере нову версію, було б непогано, якби їм сказали, що їх метод тепер приховує метод базового класу.

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

Чому приховування, а не перевизначення за замовчуванням?

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


1
FWIW, я не згоден з тим, що "[t] сама по собі виправдовує приховування методів". Іноді краще порушити зворотну сумісність. Python 3 показав, що насправді це працює, не руйнуючи занадто багато роботи. Важливо зазначити, що «зворотна сумісність за будь-яку ціну» - не єдиний розумний варіант. І ця причина переноситься на інший момент: зміна базового класу завжди буде крихкою. Порушення залежної збірки насправді може бути кращим. (Але +1 за відповідь на запитання з мого коментаря нижче фактичного запитання.)
Конрад Рудольф

@Konrad: Я згоден, що зворотна порівняльність може зашкодити. Mono вирішив відмовитись від підтримки 1.1 для наступних версій.
simendsjo

1
@Konrad: Порушення зворотної сумісності з вашою мовою дуже відрізняється від того, що полегшить людям, які використовують вашу мову, порушити зворотну сумісність із власним кодом. Приклад Еріка - другий випадок. І той, хто пише цей код, прийняв рішення про те, що він бажає зворотної сумісності. Мова повинна підтримати це рішення, якщо це можливо.
Брайан

Дай Бог, Еріку. Однак є більш конкретна причина, чому перевизначення за замовчуванням є поганим. Якщо базовий клас Base вводить новий метод Duck () (який повертає качиний об'єкт), який просто має такий самий підпис, як ваш існуючий метод Duck () (що робить Mario duck) у похідному класі Похідний (тобто відсутність зв'язку між Ви і письменник Base), ви не хочете, щоб клієнти обох класів змушували Маріо качитися, коли вони просто хочуть Качку.
jyoungdev

Таким чином, здебільшого це пов’язано із зміною базових класів та інтерфейсів.
jyoungdev

15

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

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

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


12

Варто зазначити, що єдиним наслідком newцього контексту є придушення попередження. У семантиці змін немає.

Тож одна відповідь така: нам потрібно newподати сигнал компілятору, що приховування є навмисним, і позбутися попередження.

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

Єдиною вагомою причиною, яку я можу придумати для навмисного приховування, є те, коли ім’я нав’язується вам за допомогою інтерфейсу.


6

У C # члени за замовчуванням запечатані, що означає, що ви не можете їх замінити (якщо вони не позначені ключовими словами virtualабо abstractключовими словами), і це з міркувань ефективності . Новий модифікатор використовується для явного приховати успадкований елемент.


4
Але коли ви дійсно хочете використовувати новий модифікатор?
simendsjo

1
Коли ви хочете явно приховати член, успадкований від базового класу. Приховування успадкованого члена означає, що похідна версія члена замінює версію базового класу.
Дарін Димитров

3
Але будь-які методи, що використовують базовий клас, все одно будуть викликати базову реалізацію, а не похідну. Це здається мені небезпечною ситуацією.
simendsjo

@simendsjo, @ Дарін Димитров: Мені також важко думати про варіант використання, коли нове ключове слово справді потрібне. Мені здається, що завжди є кращі шляхи. Мені також цікаво, чи приховування реалізації базового класу не є вадою дизайну, яка легко призводить до помилок.
Dirk Vollmar

2

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

Стратегія компілятора .Net полягає в тому, щоб видавати попередження, якщо щось може піти не так, щоб бути в безпеці, тому в цьому випадку, якщо перевизначення було за замовчуванням, для кожного методу перевизначення повинно бути попередження - щось на зразок 'попередження: перевірте, чи дійсно ви хочете перевизначити '.


0

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

Оновлено ... Ерік дав мені уявлення про те, як покращити цей приклад.

public interface IAction1
{
    int DoWork();
}
public interface IAction2
{
    string DoWork();
}

public class MyBase : IAction1
{
    public int DoWork() { return 0; }
}
public class MyClass : MyBase, IAction2
{
    public new string DoWork() { return "Hi"; }
}

class Program
{
    static void Main(string[] args)
    {
        var myClass = new MyClass();
        var ret0 = myClass.DoWork(); //Hi
        var ret1 = ((IAction1)myClass).DoWork(); //0
        var ret2 = ((IAction2)myClass).DoWork(); //Hi
        var ret3 = ((MyBase)myClass).DoWork(); //0
        var ret4 = ((MyClass)myClass).DoWork(); //Hi
    }
}

1
При використанні декількох інтерфейсів ви можете чітко називати методи інтерфейсу, щоб уникнути колізій. Я не розумію, що ви маєте на увазі під "різною реалізацією для одного класу", обидва мають однаковий підпис ...
simendsjo

newКлючове слово дозволяє також змінити базу успадкування для всіх дітей від цієї точки.
Matthew Whited

-1

Як зазначалося, приховування методу / властивості дозволяє змінювати речі щодо методу або властивості, які неможливо було легко змінити інакше. Одна ситуація, коли це може бути корисно, - це дозволяти успадкованому класу мати властивості читання-запису, які є лише для читання в базовому класі. Наприклад, припустимо, що базовий клас має купу властивостей лише для читання, які називаються Value1-Value40 (звичайно, реальний клас використовує кращі імена). Запечатаний нащадок цього класу має конструктор, який бере об'єкт базового класу і копіює звідти значення; клас не дозволяє їх змінювати після цього. Інший, успадковуваний, нащадок оголошує властивості читання-запису під назвою Value1-Value40, які при читанні поводяться так само, як і версії базового класу, але, коли записуються, дозволяють записувати значення.

Одне роздратування при такому підході - можливо, хтось може мені допомогти - це те, що я не знаю способу як затінити, так і замінити певну властивість у тому самому класі. Чи дозволяє це будь-яка з мов CLR (я використовую vb 2005)? Було б корисно, якби об’єкт базового класу та його властивості могли бути абстрактними, але для цього потрібно, щоб проміжний клас замінив властивості Value1 на Value40 до того, як клас-нащадок зможе їх затінити.

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