Почему вызов метода в моем производном классе вызывает метод базового класса?


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 в методе main, чтобы можно было вызывать WriteLine в классах Person и Teacher.
Богатый

6
Да, .Net автоматически закрывает окно консоли при выходе из Main (). чтобы обойти это, мы используем Console.Read () или Console.Readline (), чтобы дождаться дополнительного ввода, чтобы консоль оставалась открытой.
Капитан Кенпачи

15
@ Нет, в этом нет необходимости , но вы часто будете видеть это по этой причине: при запуске консольной программы из 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 - Дэнни Пфлюгофт

2
Отличный и очень тщательный ответ.
Ник Бугалис

8
tl; dr, который вы использовали, newкоторый нарушает наследование функции и делает новую функцию отдельной от функции суперкласса
ratchet freak

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");
    }
}

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

Когда виртуальный метод вызывается, тип объекта во время выполнения проверяется для переопределяющего члена. Вызывается переопределяющий член в самом производном классе, который может быть исходным членом, если ни один производный класс не переопределил этот член. По умолчанию методы не виртуальные. Вы не можете переопределить не виртуальный метод. Вы не можете использовать виртуальный модификатор с модификаторами static, abstract, private или override, MSDN .

Использование New для Shadowing

Вы используете новое ключевое слово вместо переопределения, это то, что делает новый

  • Если методу в производном классе не предшествуют ключевые слова new или override, компилятор выдаст предупреждение, и метод будет вести себя так, как если бы присутствовало новое ключевое слово.

  • Если методу в производном классе предшествует ключевое слово new, метод определяется как независимый от метода в базовом классе. Эта статья MSDN объясняет это очень хорошо.

Раннее связывание В.С. Позднее связывание

У нас есть раннее связывание во время компиляции для обычного метода (не виртуального), что является текущим случаем, когда компилятор будет связывать вызов метода базового класса, который является методом ссылочного типа (базовый класс), вместо объекта, который содержится в ссылке на базовый класс. класс т.е. объект производного класса . Это потому, что ShowInfoэто не виртуальный метод. Позднее связывание выполняется во время выполнения для (виртуальный / переопределенный метод) с использованием таблицы виртуальных методов (vtable).

Для нормальной функции компилятор может определить ее числовое местоположение в памяти. Затем, когда функция вызывается, она может генерировать инструкцию для вызова функции по этому адресу.

Для объекта, который имеет любые виртуальные методы, компилятор сгенерирует v-таблицу. По сути, это массив, содержащий адреса виртуальных методов. Каждый объект, имеющий виртуальный метод, будет содержать скрытый элемент, сгенерированный компилятором, который является адресом v-таблицы. Когда вызывается виртуальная функция, компилятор определяет, какова позиция соответствующего метода в v-таблице. Затем он сгенерирует код для просмотра в v-таблице объектов и вызовет виртуальный метод в этой позиции, Reference .


7

Я хочу построить ответ Ахратта . Для полноты, разница в том, что OP ожидает, что newключевое слово в методе производного класса переопределит метод базового класса. На самом деле он скрывает метод базового класса.

В C #, как упоминалось в другом ответе, переопределение традиционного метода должно быть явным; метод базового класса должен быть помечен как, virtualа производный класс должен, в частности, overrideметод базового класса. Если это сделано, тогда не имеет значения, рассматривается ли объект как экземпляр базового класса или производного класса; производный метод найден и вызван. Это делается аналогично C ++; метод, помеченный как «виртуальный» или «переопределить», при компиляции разрешается «поздно» (во время выполнения) путем определения фактического типа ссылочного объекта и обхода иерархии объектов вниз по дереву от типа переменной к фактическому типу объекта, найти наиболее производную реализацию метода, определенного типом переменной.

Это отличается от Java, который допускает «неявные переопределения»; например, методы экземпляра (нестатические), простое определение метода с той же сигнатурой (имя и число / тип параметров) приведет к тому, что подкласс переопределит суперкласс.

Поскольку часто полезно расширить или переопределить функциональность не виртуального метода, которым вы не управляете, C # также включает newключевое слово contextual. newКлючевое слово «шкура» родительский метод не переопределяет его. Любой наследуемый метод может быть скрыт независимо от того, виртуальный он или нет; это позволяет вам, разработчику, использовать элементы, которые вы хотите унаследовать от родителя, без необходимости обходить тех, которые у вас нет, и в то же время позволяет вам предоставлять тот же «интерфейс» потребителям вашего кода.

Сокрытие работает аналогично переопределению с точки зрения человека, использующего ваш объект на уровне наследования или ниже, на котором определен метод сокрытия. Из примера вопроса, кодер, создающий Учителя и сохраняющий эту ссылку в переменной типа Учитель, увидит поведение реализации ShowInfo () от Учителя, которая скрывает его от Person. Однако кто-то, работающий с вашим объектом в коллекции записей Person (как и вы), увидит поведение реализации Person в ShowInfo (); Поскольку метод Учителя не переопределяет его родителя (что также потребует, чтобы Person.ShowInfo () был виртуальным), код, работающий на уровне абстракции Person, не найдет реализацию Учителя и не будет использовать ее.

Кроме того, не только 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 (он не может; Bar sealedэто), он не виден во время выполнения при взгляде «сверху вниз», поэтому вместо этого вызывается реализация Bar. Обратите внимание, что Baz не должен использовать newключевое слово; вы получите предупреждение компилятора, если опустите ключевое слово, но подразумеваемое поведение в C # - скрыть родительский метод. baz2это Bai, который переопределяетBaz «ыnewреализация, поэтому его поведение похоже на , который не имеет ни одной из основных реализаций и просто использует реализацию своего родителя.foo2«S; фактическая реализация типа объекта в Bai называется. bai2is Bat, который снова скрывает Baiреализацию метода своего родителя , и ведет себя так же, как bar2если бы реализация Bai не была запечатана, поэтому теоретически Bat мог бы переопределить, а не скрыть метод. Наконец, bat2это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

Новое ключевое слово сказать , что метод в текущем классе будет работать только если у вас есть экземпляр класса Учителя , хранящегося в переменной типа Учителя. Или вы можете запустить его с помощью приведений: ((Teacher) Person) .ShowInfo ()


1

Тип переменной'acher 'здесь такой, typeof(Person)и этот тип ничего не знает о классе Teacher и не пытается искать какие-либо методы в производных типах. Для вызова метода класса учителя вы должны бросить переменную: (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

Опираясь на отличную демонстрацию Кейта С. и качественные ответы всех остальных, а также для полноты изложения, давайте продолжим и добавим явные реализации интерфейса, чтобы продемонстрировать, как это работает. Рассмотрим ниже:

пространство имен 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

teacher1: я учитель == LinqConsoleApp.Teacher

person4: я человек == LinqConsoleApp.Person

person3: я интерфейс Person == LinqConsoleApp.Person

Обратите внимание на две вещи:
метод Teacher.ShowInfo () опускает ключевое слово new. Когда новый опущен, поведение метода такое же, как если бы ключевое слово new было определено явно.

Вы можете использовать ключевое слово override только вместе с виртуальным ключевым словом. Метод базового класса должен быть виртуальным. Или абстрактный, в этом случае класс также должен быть абстрактным.

person получает базовую реализацию ShowInfo, потому что класс Teacher не может переопределить базовую реализацию (без виртуального объявления), а person - .GetType (Teacher), поэтому он скрывает реализацию класса Teacher.

Учитель получает производную реализацию Учителя от ShowInfo, потому что учитель, потому что это Typeof (Учитель), а не на уровне наследования Person.

person1 получает производную реализацию Teacher, потому что это .GetType (Teacher), а подразумеваемое новое ключевое слово скрывает базовую реализацию.

person2 также получает производную реализацию Учителя, даже если она реализует IPerson и получает явное приведение к IPerson. Это опять-таки потому, что класс Teacher явно не реализует метод IPerson.ShowInfo ().

teacher1 также получает производную реализацию Учителя, потому что это .GetType (Учитель).

Только person3 получает реализацию ShowInfo для IPerson, потому что только класс Person явно реализует метод, а person3 является экземпляром типа IPerson.

Чтобы явным образом реализовать интерфейс, вы должны объявить экземпляр var целевого типа интерфейса, а класс должен явно реализовать (полностью квалифицировать) член (ы) интерфейса.

Обратите внимание, что даже person4 не получает реализацию IPerson.ShowInfo. Это потому, что, хотя person4 является .GetType (Person) и хотя Person реализует IPerson, person4 не является экземпляром IPerson.


Я вижу, что форматирование кода правильно представляет собой небольшую проблему. Нет времени, чтобы украсить это прямо сейчас ...
Стил

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.