Я хочу побудувати відповідь Ахрата . Для повноти різниця полягає в тому, що ОП очікує, що newключове слово у методі похідного класу замінить метод базового класу. Насправді це приховує метод базового класу.
У З #, як згадується ще одна відповідь, традиційний метод переосмислення повинен бути явним; метод базового класу повинен бути позначений як, virtualа похідний клас повинен конкретноoverride метод базового класу. Якщо це зроблено, то не має значення, чи розглядається об'єкт як екземпляр базового класу або похідного класу; знайдений і названий похідний метод. Це робиться аналогічно, як у C ++; метод, позначений "віртуальний" або "переосмислити", коли компілюється, вирішується "пізно" (під час виконання) шляхом визначення фактичного типу посиланого об'єкта та переміщення ієрархії об'єктів вниз по дереву від типу змінної до фактичного типу об'єкта, знайти найбільш похідну реалізацію методу, визначеного типом змінної.
Це відрізняється від Java, що дозволяє "неявні переопределення"; наприклад, методи (нестатичні), просто визначення методу тієї самої підпису (ім'я та номер / тип параметрів) призведе до того, що підклас замінить надклас.
Оскільки часто корисно розширювати або змінювати функціональність невіртуального методу, яким ви не керуєте, C # також включає newконтекстне ключове слово. newКлючове слово «шкура» батьківський метод не перекриває його. Будь-який успадкований метод можна приховати, віртуальний він чи ні; це дозволяє вам, розробнику, користуватися членами, які ви хочете успадкувати від батьків, не обробляючи тих, яких ви не маєте, одночасно дозволяючи представляти той самий "інтерфейс" споживачам вашого коду.
Приховування працює аналогічно переосмисленню з точки зору людини, яка використовує ваш об’єкт на рівні або нижче рівня успадкування, на якому визначено спосіб приховування. З прикладу запитання, кодер, який створює Вчителя та зберігає цю посилання у змінній типу Вчитель, побачить поведінку реалізації ShowInfo () від Teacher, яка приховує цю від Person. Однак хтось, хто працює з вашим об'єктом у колекції записів персоналу (як ви є), побачить поведінку Person впровадження ShowInfo (); оскільки метод вчителя не перекриває його батьківського (що також вимагає, щоб Person.ShowInfo () було віртуальним), код, що працює на рівні особи абстракції, не знайде вчителівської реалізації та не використовуватиме його.
Крім того, не тільки newключове слово зробить це явно, C # дозволяє приховати прихований метод; просто визначивши метод з тією ж підписом, що і метод батьківського класу, без overrideабо newприховає його (хоча він створить попередження компілятора або скаргу від певних помічників рефакторингу, таких як ReSharper або CodeRush). Це компромісний дизайн дизайнерів C #, придуманий між чіткими перекриттями C ++ та неявними, і незважаючи на те, що Java, і хоч він і елегантний, він не завжди сприймає поведінку, яку ви очікували, якщо ви походите з фонової мови на будь-якій із старих мов.
Ось новий матеріал: Це стає складним, коли ви поєднуєте ці два ключові слова у довгому ланцюжку успадкування. Розглянемо наступне:
class Foo { public virtual void DoFoo() { Console.WriteLine("Foo"); } }
class Bar:Foo { public override sealed void DoFoo() { Console.WriteLine("Bar"); } }
class Baz:Bar { public virtual void DoFoo() { Console.WriteLine("Baz"); } }
class Bai:Baz { public override void DoFoo() { Console.WriteLine("Bai"); } }
class Bat:Bai { public new void DoFoo() { Console.WriteLine("Bat"); } }
class Bak:Bat { }
Foo foo = new Foo();
Bar bar = new Bar();
Baz baz = new Baz();
Bai bai = new Bai();
Bat bat = new Bat();
foo.DoFoo();
bar.DoFoo();
baz.DoFoo();
bai.DoFoo();
bat.DoFoo();
Console.WriteLine("---");
Foo foo2 = bar;
Bar bar2 = baz;
Baz baz2 = bai;
Bai bai2 = bat;
Bat bat2 = new Bak();
foo2.DoFoo();
bar2.DoFoo();
baz2.DoFoo();
bai2.DoFoo();
Console.WriteLine("---");
Foo foo3 = bak;
Bar bar3 = bak;
Baz baz3 = bak;
Bai bai3 = bak;
Bat bat3 = bak;
foo3.DoFoo();
bar3.DoFoo();
baz3.DoFoo();
bai3.DoFoo();
bat3.DoFoo();
Вихід:
Foo
Bar
Baz
Bai
Bat
---
Bar
Bar
Bai
Bai
Bat
---
Bar
Bar
Bai
Bai
Bat
Перший набір з п'яти - все очікуване; оскільки кожен рівень має реалізацію і посилається на об'єкт того ж типу, що і інстанціювався, час виконання вирішує кожен виклик на рівень спадкування, на який посилається тип змінної.
Другий набір з п'яти є результатом присвоєння кожному екземпляру змінної безпосереднього типу батьків. Тепер деякі проблеми в поведінці вилітають; foo2, який фактично є Barакторським складом як Foo, все ще знайде більш похідний метод фактичного типу об'єкта Bar. bar2є Baz, але на відміну від foo2, тому що Baz явно не переосмислює реалізацію Bar (не може; заборонити sealedце), вона не бачиться під час виконання під час перегляду "зверху вниз", тому реалізація Bar називається замість цього. Зауважте, що Базу не потрібно використовувати newключове слово; ви отримаєте попередження про компілятор, якщо ви пропустите ключове слово, але мається на увазі поведінка в C # - це приховати батьківський метод. baz2- це Bai, що переосмислюєBaz snewреалізація, тому її поведінка схожа на foo2s; називається реальна реалізація типу об'єкта в Баї. bai2це a Bat, який знову приховує реалізацію свого батьківського Baiметоду, і він поводиться так само, як bar2навіть якщо реалізація Бая не запечатана, тому теоретично Бат міг би замінити метод замість прихованого методу. Нарешті, bat2це a Bak, який не має жодної переважаючої реалізації будь-якого виду і просто використовує його батьківський.
Третій набір із п'яти ілюструє повну поведінку роздільної здатності зверху вниз. Насправді все посилається на екземпляр найбільш похідного класу в ланцюжку, Bakале роздільна здатність на кожному рівні змінного типу виконується, починаючи з цього рівня ланцюга успадкування і переглядаючи до найбільш похідного явного переопрацювання методу, які є ті , в Bar, Baiі Bat. Метод приховування таким чином "розриває" переважаючий ланцюг спадкування; ви повинні працювати з об'єктом на рівні або нижче рівня успадкування, який приховує метод, щоб використовувати метод приховування. В іншому випадку прихований метод "розкривається" і використовується замість цього.