Чому виклик методу у моєму похідному класі називає метод базового класу?


146

Розглянемо цей код:

class Program
{
    static void Main(string[] args)
    {
        Person person = new Teacher();
        person.ShowInfo();
        Console.ReadLine();
    }
}

public class Person
{
    public void ShowInfo()
    {
        Console.WriteLine("I am Person");
    }
}
public class Teacher : Person
{
    public new void ShowInfo()
    {
        Console.WriteLine("I am Teacher");
    }
}

Коли я запускаю цей код, виводиться наступне:

Я Людина

Однак ви можете бачити, що це екземпляр Teacher, а не Person. Чому код робить це?


3
Питання від особи Java: це Console.ReadLine (); необхідний для цього прикладу?
Багатий

2
@Shahrooz Я не можу відповісти на ваше запитання - я не знаю C #. Я задавав дуже тривіальне питання C #, чи потрібен дзвінок на ReadLine в основному методі, щоб можна було викликати WriteLine в класах Person and Teacher.
Багатий

6
Так, .Net автоматично закриває вікно консолі, коли Main () виходить. Щоб обійти це, ми використовуємо Console.Read () або Console.Readline (), щоб дочекатися додаткового введення, щоб консоль залишалася відкритою.
Капітан Кенпачі

15
@Rich ні, це не обов'язково , але ви часто будете бачити це з цієї причини: при запуску консольної програми від Visual Studio, після завершення програми вікно команд замикається відразу, тому якщо ви хочете побачити вихід програми, вам потрібно сказати про це чекати.
AakashM

1
@AakashM Спасибі - я провожу час у Eclipse, де консоль є частиною вікна Eclipse, і тому не закривається. Це має ідеальний сенс.
Багатий

Відповіді:


368

Існує різниця між newта virtual/override .

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

Ілюстрація реалізації методів

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

1. Абстрактні заняття

Перший - це abstract. abstractметоди просто вказують нікуди:

Ілюстрація абстрактних занять

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

public abstract class Person
{
    public abstract void ShowInfo();
}

public class Teacher : Person
{
    public override void ShowInfo()
    {
        Console.WriteLine("I am a teacher!");
    }
}

public class Student : Person
{
    public override void ShowInfo()
    {
        Console.WriteLine("I am a student!");
    }
}

Якщо викликано, поведінка ShowInfoзмінюється залежно від реалізації:

Person person = new Teacher();
person.ShowInfo();    // Shows 'I am a teacher!'

person = new Student();
person.ShowInfo();    // Shows 'I am a student!'

І Students, і Teachers є Persons, але вони поводяться по-різному, коли їх просять повідомити інформацію про себе. Однак спосіб попросити їх повідомити інформацію про них однаковий: Використання Personінтерфейсу класу.

Отже, що відбувається за лаштунками, коли ви успадковуєте Person? Під час реалізації ShowInfoпокажчик більше не вказує нікуди , тепер вказує на фактичну реалізацію! Створюючи Studentекземпляр, він вказує на Students ShowInfo:

Ілюстрація успадкованих методів

2. Віртуальні методи

Другий спосіб - використання virtualметодів. Поведінка однакова, за винятком того, що ви надаєте необов'язкове виконання за замовчуванням у базовому класі. Класи з virtualчленами можуть бути інстанційними, однак успадковані класи можуть забезпечувати різні реалізації. Ось як насправді повинен виглядати ваш код:

public class Person
{
    public virtual void ShowInfo()
    {
        Console.WriteLine("I am a person!");
    }
}

public class Teacher : Person
{
    public override void ShowInfo()
    {
        Console.WriteLine("I am a teacher!");
    }
}

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

Ілюстрація віртуального члена всередині базового класу

Слід зауважити, що наразі це не відрізняється від першого зображення. Це тому, що virtualметод вказує на реалізацію " стандартним способом ". Використовуючи virtual, ви можете сказати Persons, що вони можуть (не повинні ) забезпечувати іншу реалізацію ShowInfo. Якщо ви надаєте іншу реалізацію (використовуючи override), як я це робив для Teacherвищезазначеного, зображення виглядатиме так само, як і для abstract. Уявіть, ми не запропонували спеціальну реалізацію для Students:

public class Student : Person
{
}

Код буде називатися так:

Person person = new Teacher();
person.ShowInfo();    // Shows 'I am a teacher!'

person = new Student();
person.ShowInfo();    // Shows 'I am a person!'

А зображення для Studentвиглядатиме так:

Ілюстрація реалізації за замовчуванням методу з використанням віртуального ключового слова

3. Чарівне `нове` ключове слово aka" Shadowing "

newбільше хакер навколо цього. Ви можете надати методи в узагальнених класах, які мають ті ж назви, що і методи в базовому класі / інтерфейсі. Обидва вказують на власну, власну реалізацію:

Ілюстрація "навпаки" за допомогою нового ключового слова

Реалізація схожа на ту, яку ви надали. Поведінка відрізняється залежно від способу доступу до методу:

Teacher teacher = new Teacher();
Person person = (Person)teacher;

teacher.ShowInfo();    // Prints 'I am a teacher!'
person.ShowInfo();     // Prints 'I am a person!'

Таку поведінку можна розшукувати, але у вашому випадку вона вводить в оману.

Я сподіваюся, що це робить для вас зрозумілішими речі!


9
Дякуємо за вашу чудову відповідь

6
Що ви використовували для створення цих діаграм?
BlueRaja - Danny Pflughoeft

2
Відмінна і дуже ретельна відповідь.
Nik Bougalis

8
tl; dr, який ви використовували, newякий порушує успадкування функції та робить нову функцію окремою від функції надкласу
храповик виродка

3
@Taymon: Насправді ні ... Я просто хотів уточнити, що зараз дзвінок йде проти Person, а не Student;)
Карстен

45

Поліморфізм підтипу в C # використовує явну віртуальність, схожу на C ++, але на відміну від Java. Це означає, що ви явно повинні позначати методи як перезаписні (тобто virtual). У C # ви також повинні чітко позначити переосмислювальні методи як переосмислення (тобто override) для запобігання помилок.

public class Person
{
    public virtual void ShowInfo()
    {
        Console.WriteLine("I am Person");
    }
}

public class Teacher : Person
{
    public override void ShowInfo()
    {
        Console.WriteLine("I am Teacher");
    }
}

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


4
Хто сказав, що ОП знає, що це означає.
Коул Джонсон

@ColeJohnson Я додам уточнення.

25

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

public class Person
{
    public virtual void ShowInfo()
    {
        Console.WriteLine("I am Person");
    }
}
public class Teacher : Person
{
    public override void ShowInfo()
    {
        Console.WriteLine("I am Teacher");
    }
}

Віртуальні методи

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

Використання Нового для затінення

Ви використовуєте нове ключове слово замість заміщення, це те, що робить нове

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

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

Раннє зв'язування VS Пізнє зв'язування

У нас є раннє прив'язування в час компіляції для звичайного методу (не віртуального), який є випадковим випадком, компілятор буде прив'язувати виклик до методу базового класу, який є методом опорного типу (базовий клас), а не об'єктом, утримується в референції бази class, тобто похідний об'єкт класу . Це тому, що ShowInfoце не віртуальний метод. Пізня прив'язка виконується під час виконання для (віртуальний / переосмислений метод) за допомогою таблиці віртуальних методів (vtable).

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

Для об’єкта, у якого є будь-які віртуальні методи, компілятор генерує v-таблицю. По суті це масив, який містить адреси віртуальних методів. Кожен об'єкт, у якого є віртуальний метод, буде містити прихований член, сформований компілятором, що є адресою v-таблиці. Коли викликується віртуальна функція, компілятор розробить, яку позицію має відповідний метод у v-таблиці. Потім він буде генерувати код для перегляду v-таблиці об'єктів і виклику віртуального методу в цій позиції, Reference .


7

Я хочу побудувати відповідь Ахрата . Для повноти різниця полягає в тому, що ОП очікує, що 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. Метод приховування таким чином "розриває" переважаючий ланцюг спадкування; ви повинні працювати з об'єктом на рівні або нижче рівня успадкування, який приховує метод, щоб використовувати метод приховування. В іншому випадку прихований метод "розкривається" і використовується замість цього.


4

Прочитайте, будь ласка, про поліморфізм у C #: Поліморфізм (Посібник з програмування C #)

Це приклад звідти:

Коли використовується нове ключове слово, нові члени класу викликаються замість замінених членів базового класу. Ці члени базового класу називаються прихованими членами. Прихованих членів класу все ще можна викликати, якщо екземпляр похідного класу передається екземпляру базового класу. Наприклад:

DerivedClass B = new DerivedClass();
B.DoWork();  // Calls the new method.

BaseClass A = (BaseClass)B;
A.DoWork();  // Calls the old method.

3

Вам потрібно зробити це, virtualа потім змінити цю функцію в Teacher. Оскільки ви успадковуєте та використовуєте базовий вказівник для посилання на похідний клас, його потрібно переосмислити за допомогою virtual. newпризначений для приховування baseметоду класу на похідному посиланні класу, а не на baseпосилання на клас.


3

Я хотів би додати ще кілька прикладів, щоб розширити інформацію про це. Сподіваюся, що це теж допомагає:

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

namespace TestApp
{
    class Program
    {
        static void Main(string[] args)
        {
            A a = new A();
            a.foo();        // A.foo()
            a.foo2();       // A.foo2()

            a = new B();    
            a.foo();        // B.foo()
            a.foo2();       // A.foo2()
            //a.novel() is not available here

            a = new C();
            a.foo();        // C.foo()
            a.foo2();       // A.foo2()

            B b1 = (B)a;    
            b1.foo();       // C.foo()
            b1.foo2();      // B.foo2()
            b1.novel();     // B.novel()

            Console.ReadLine();
        }
    }


    class A
    {
        public virtual void foo()
        {
            Console.WriteLine("A.foo()");
        }

        public void foo2()
        {
            Console.WriteLine("A.foo2()");
        }
    }

    class B : A
    {
        public override void foo()
        {
            // This is an override
            Console.WriteLine("B.foo()");
        }

        public new void foo2()      // Using the 'new' keyword doesn't make a difference
        {
            Console.WriteLine("B.foo2()");
        }

        public void novel()
        {
            Console.WriteLine("B.novel()");
        }
    }

    class C : B
    {
        public override void foo()
        {
            Console.WriteLine("C.foo()");
        }

        public new void foo2()
        {
            Console.WriteLine("C.foo2()");
        }
    }
}

Ще одна невелика аномалія полягає в тому, що для наступного рядка коду:

A a = new B();    
a.foo(); 

Компілятор VS (intellisense) буде показувати a.foo () як A.foo ().

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

Цей зразок коду повинен допомогти розмежувати ці застереження!


2

C # відрізняється від java у поведінці класу батьків / дитини. За замовчуванням у Java всі методи є віртуальними, тому потрібна поведінка підтримується поза вікном.

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


2

Нове ключове слово сказати , що метод в поточному класі буде працювати тільки якщо у вас є екземпляр класу Вчителі , що зберігається в змінної типу Вчителі. Або ви можете запустити це за допомогою кастингу: ((вчитель) особа). Showhow ()


1

Тип змінної "учитель" тут є, typeof(Person)і цей тип нічого не знає про клас вчителя і не намагається шукати будь-які методи у похідних типах. Для виклику методу класу вчителя ви повинні кинути змінну: (person as Teacher).ShowInfo().

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

public class Program
{
    private static void Main(string[] args)
    {
        Person teacher = new Teacher();
        teacher.ShowInfo();

        Person incognito = new IncognitoPerson ();
        incognito.ShowInfo();

        Console.ReadLine();
    }
}

public class Person
{
    public virtual void ShowInfo()
    {
        Console.WriteLine("I am Person");
    }
}

public class Teacher : Person
{
    public override void ShowInfo()
    {
        Console.WriteLine("I am Teacher");
    }
}

public class IncognitoPerson : Person
{

}

1

Можливо, буде пізно ... Але питання просте, і відповідь має бути однакового рівня складності.

У вашому коді змінна людина нічого не знає про Teacher.ShowInfo (). Немає можливості викликати останній метод із посилання базового класу, оскільки це не віртуально.

Є корисний підхід до успадкування - спробуйте уявити, що ви хочете сказати зі своєю ієрархією коду. Спробуйте також уявити, що говорить той чи інший інструмент про себе. Наприклад, якщо ви додаєте віртуальну функцію в базовий клас, ви вважаєте: 1. вона може мати реалізацію за замовчуванням; 2. він може бути повторно застосований у похідному класі. Якщо ви додасте абстрактну функцію, це означає лише одне - підклас повинен створити реалізацію. Але якщо у вас є звичайна функція - ви не очікуєте, що хтось змінить її виконання.


0

Компілятор робить це, оскільки не знає, що це Teacher. Все, що він знає, це те, що це Personщось або похідне від нього. Тому все, що він може зробити, це викликати Person.ShowInfo()метод.


0

Просто хотів дати коротку відповідь -

Ви повинні використовувати virtualі overrideв класах, які можна було б перекрити. Використовуйте virtualдля методів, які можуть бути відмінені дочірніми класами, і використовуйте overrideдля методів, які повинні перекривати такі virtualметоди.


0

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

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

public class inheritance{

    public static void main(String[] args){

        Person person = new Teacher();
        person.ShowInfo();
    }
}

class Person{

    public void ShowInfo(){
        System.out.println("I am Person");
    }
}

class Teacher extends Person{

    public void ShowInfo(){
        System.out.println("I am Teacher");
    }
}

0

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

простір імен LinqConsoleApp {

class Program
{

    static void Main(string[] args)
    {


        Person person = new Teacher();
        Console.Write(GetMemberName(() => person) + ": ");
        person.ShowInfo();

        Teacher teacher = new Teacher();
        Console.Write(GetMemberName(() => teacher) + ": ");
        teacher.ShowInfo();

        IPerson person1 = new Teacher();
        Console.Write(GetMemberName(() => person1) + ": ");
        person1.ShowInfo();

        IPerson person2 = (IPerson)teacher;
        Console.Write(GetMemberName(() => person2) + ": ");
        person2.ShowInfo();

        Teacher teacher1 = (Teacher)person1;
        Console.Write(GetMemberName(() => teacher1) + ": ");
        teacher1.ShowInfo();

        Person person4 = new Person();
        Console.Write(GetMemberName(() => person4) + ": ");
        person4.ShowInfo();

        IPerson person3 = new Person();
        Console.Write(GetMemberName(() => person3) + ": ");
        person3.ShowInfo();

        Console.WriteLine();

        Console.ReadLine();

    }

    private static string GetMemberName<T>(Expression<Func<T>> memberExpression)
    {
        MemberExpression expressionBody = (MemberExpression)memberExpression.Body;
        return expressionBody.Member.Name;
    }

}
interface IPerson
{
    void ShowInfo();
}
public class Person : IPerson
{
    public void ShowInfo()
    {
        Console.WriteLine("I am Person == " + this.GetType());
    }
    void IPerson.ShowInfo()
    {
        Console.WriteLine("I am interface Person == " + this.GetType());
    }
}
public class Teacher : Person, IPerson
{
    public void ShowInfo()
    {
        Console.WriteLine("I am Teacher == " + this.GetType());
    }
}

}

Ось результат:

людина: Я Людина == LinqConsoleApp.Teacher

вчитель: Я вчитель == LinqConsoleApp.Teacher

person1: Я вчитель == LinqConsoleApp.Teacher

person2: Я вчитель == LinqConsoleApp.Teacher

учитель1: Я вчитель == LinqConsoleApp.Teacher

person4: Я Людина == LinqConsoleApp.Person

person3: Я інтерфейс Person == LinqConsoleApp.Person

Дві речі, які слід зазначити:
Метод Teacher.ShowInfo () ухиляє нове ключове слово. Якщо пропущено нове, поведінка методу така сама, як якщо б нове ключове слово було чітко визначено.

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

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

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

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

person2 також отримує похідну вчительську реалізацію, навіть незважаючи на те, що вона реалізує IPerson, і вона отримує явну роль у IPerson. Це знову ж таки тому, що клас вчителя не реалізує явно метод IPerson.ShowInfo ().

вчитель1 також отримує похідну програму вчителя, тому що це .GetType (вчитель).

Тільки person3 отримує реалізацію ShowInfo IPerson, оскільки лише клас Person реально реалізує метод, а person3 - це екземпляр типу IPerson.

Для того, щоб явно реалізувати інтерфейс, ви повинні оголосити var-екземпляр цільового типу інтерфейсу, а клас повинен явно реалізувати (повністю кваліфікувати) членів (-ів) інтерфейсу.

Помітьте, що навіть person4 не отримує реалізацію IPerson.ShowInfo. Це тому, що, хоча person4 є .GetType (Person) і навіть незважаючи на те, що Person реалізує IPerson, person4 не є екземпляром IPerson.


Я бачу, що форматування коду належним чином представляє певну проблему. Немає часу, щоб зараз це зробити ...
steely

0

Зразок LinQPad для сліпого запуску та зменшення дублювання коду, що, на мою думку, ви намагалися зробити.

void Main()
{
    IEngineAction Test1 = new Test1Action();
    IEngineAction Test2 = new Test2Action();
    Test1.Execute("Test1");
    Test2.Execute("Test2");
}

public interface IEngineAction
{
    void Execute(string Parameter);
}

public abstract class EngineAction : IEngineAction
{
    protected abstract void PerformAction();
    protected string ForChildren;
    public void Execute(string Parameter)
    {  // Pretend this method encapsulates a 
       // lot of code you don't want to duplicate 
      ForChildren = Parameter;
      PerformAction();
    }
}

public class Test1Action : EngineAction
{
    protected override void PerformAction()
    {
        ("Performed: " + ForChildren).Dump();
    }
}

public class Test2Action : EngineAction
{
    protected override void PerformAction()
    {
        ("Actioned: " + ForChildren).Dump();
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.