Dlaczego wywołanie metody w mojej klasie pochodnej wywołuje metodę klasy bazowej?


146

Rozważ ten kod:

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

Kiedy uruchamiam ten kod, wyprowadzane jest następujące polecenie:

Jestem Osobą

Jednak widać, że jest to przykład Teacher, a nie Person. Dlaczego kod to robi?


3
Pytanie od osoby Java: to Console.ReadLine (); potrzebne w tym przykładzie?
Rich

2
@Shahrooz Nie mogę odpowiedzieć na twoje pytanie - nie znam C #. Zadałem bardzo banalne pytanie C #, czy wywołanie ReadLine w metodzie main jest konieczne, aby móc wywołać WriteLine w klasach Person i Teacher.
Rich

6
Tak, .Net automatycznie zamyka okno konsoli po wyjściu Main (). Aby obejść ten problem, używamy Console.Read () lub Console.Readline (), aby czekać na dodatkowe dane wejściowe, aby konsola pozostała otwarta.
Kapitan Kenpachi,

15
@Rich nie, nie jest to konieczne , ale często to widzisz z tego powodu: podczas uruchamiania programu konsoli z Visual Studio, po zakończeniu programu okno poleceń zamyka się od razu, więc jeśli chcesz zobaczyć wyjście programu, musisz to powiedzieć czekać.
AakashM,

1
@AakashM Thanks - spędzam czas w Eclipse, gdzie konsola jest częścią okna Eclipse i dlatego się nie zamyka. To ma sens.
Rich

Odpowiedzi:


368

Istnieje różnica między newi virtual/ override.

Można sobie wyobrazić, że klasa po utworzeniu jest niczym innym jak tabelą wskaźników wskazujących na rzeczywistą implementację jej metod. Poniższy obraz powinien to całkiem dobrze zobrazować:

Ilustracja implementacji metod

Teraz można zdefiniować metodę na różne sposoby. Każdy zachowuje się inaczej, gdy jest używany z dziedziczeniem. Standardowy sposób zawsze działa tak, jak pokazano na powyższym obrazku. Jeśli chcesz zmienić to zachowanie, możesz dołączyć różne słowa kluczowe do swojej metody.

1. Klasy abstrakcyjne

Pierwszy to abstract. abstractmetody po prostu wskazują donikąd:

Ilustracja klas abstrakcyjnych

Jeśli Twoja klasa zawiera abstrakcyjne elementy członkowskie, również musi być oznaczona jako abstract, w przeciwnym razie kompilator nie skompiluje aplikacji. Nie można tworzyć instancji abstractklas, ale można po nich dziedziczyć i tworzyć instancje dziedziczonych klas oraz uzyskiwać do nich dostęp przy użyciu definicji klasy bazowej. W Twoim przykładzie wyglądałoby to następująco:

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

W przypadku wywołania zachowanie ShowInforóżni się w zależności od implementacji:

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

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

Zarówno Students, jak i Teachers są Persons, ale zachowują się inaczej, gdy są proszeni o podpowiedzenie informacji o sobie. Jednak sposób poproszenia ich o podanie informacji jest taki sam: za pomocą Personinterfejsu klasowego.

Więc co dzieje się za kulisami, kiedy dziedziczysz po Person? Podczas implementacji ShowInfowskaźnik nie wskazuje już nigdzie , teraz wskazuje rzeczywistą implementację! Podczas tworzenia Studentinstancji wskazuje na Students ShowInfo:

Ilustracja metod dziedziczonych

2. Metody wirtualne

Drugim sposobem jest użycie virtualmetod. Zachowanie jest takie samo, z wyjątkiem tego, że podajesz opcjonalną domyślną implementację w swojej klasie bazowej. Klasy z virtualczłonkami mogą być instancjonowane, jednak klasy dziedziczone mogą zapewniać różne implementacje. Oto jak powinien wyglądać Twój kod, aby działał:

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

Kluczowa różnica polega na tym, że element bazowy Person.ShowInfonie wskazuje już nigdzie . Jest to również powód, dla którego możesz tworzyć instancje Person(i dlatego nie musi być już oznaczane jako abstract):

Ilustracja wirtualnego elementu członkowskiego wewnątrz klasy bazowej

Należy zauważyć, że na razie nie wygląda to inaczej niż na pierwszym obrazku. Dzieje się tak, ponieważ virtualmetoda wskazuje na implementację „ w standardowy sposób ”. Używając virtualmożna powiedzieć Persons, że oni mogą (nie muszą ) zapewniać innej implementacji dla ShowInfo. Jeśli podasz inną implementację (używając override), tak jak zrobiłem dla Teacherpowyższego, obraz będzie wyglądał tak samo jak w przypadku abstract. Wyobraź sobie, że nie dostarczyliśmy niestandardowej implementacji dla Students:

public class Student : Person
{
}

Kod zostałby nazwany następująco:

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

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

Obrazek Studentwyglądałby tak:

Ilustracja domyślnej implementacji metody z użyciem słowa kluczowego virtual

3. Magiczne słowo kluczowe „nowe”, znane również jako „Cieniowanie”

newto bardziej hack wokół tego. Możesz udostępniać metody w klasach uogólnionych, które mają takie same nazwy jak metody w klasie bazowej / interfejsie. Obie wskazują na własną, niestandardową implementację:

Ilustracja przedstawiająca „obejście” przy użyciu słowa kluczowego new

Implementacja wygląda jak ta, którą podałeś. Zachowanie różni się w zależności od sposobu uzyskiwania dostępu do metody:

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

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

Takie zachowanie może być pożądane, ale w twoim przypadku jest mylące.

Mam nadzieję, że dzięki temu wszystko będzie dla Ciebie bardziej zrozumiałe!


9
Dziękuję za świetną odpowiedź

6
Czego użyłeś do wygenerowania tych diagramów?
BlueRaja - Danny Pflughoeft

2
Doskonała i bardzo dokładna odpowiedź.
Nik Bougalis

8
tl; dr użyłeś, newco przerywa dziedziczenie funkcji i sprawia, że ​​nowa funkcja jest oddzielna od funkcji nadklasy
ratchet freak

3
@Taymon: Właściwie nie ... Chciałem tylko wyjaśnić, że wezwanie jest teraz przeciwne Person, nie Student;)
Carsten

45

Polimorfizm podtypów w C # wykorzystuje jawną wirtualność, podobnie jak w C ++, ale w przeciwieństwie do Javy. Oznacza to, że musisz jawnie oznaczyć metody jako zastępowalne (tj virtual.). W C # musisz również jawnie oznaczyć metody zastępowania jako zastępujące (tj. override), Aby zapobiec literówkom.

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

W kodzie w swoim pytaniu używasz new, co powoduje cieniowanie zamiast nadpisywania. Cieniowanie wpływa jedynie na semantykę czasu kompilacji, a nie na semantykę środowiska wykonawczego, stąd niezamierzone wyniki.


4
Kto powiedział, że OP wie, co to znaczy.
Cole Johnson,

@ColeJohnson Dodam wyjaśnienie.

25

Musisz uczynić tę metodę wirtualną i musisz przesłonić funkcję w klasie potomnej, aby wywołać metodę obiektu klasy, którą umieściłeś w odwołaniu do klasy nadrzędnej.

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

Metody wirtualne

Gdy wywoływana jest metoda wirtualna, typ czasu wykonywania obiektu jest sprawdzany pod kątem zastępującego elementu członkowskiego. Wywoływany jest zastępujący element członkowski w klasie najbardziej pochodnej, który może być oryginalnym elementem członkowskim, jeśli żadna klasa pochodna nie przesłania elementu członkowskiego. Domyślnie metody nie są wirtualne. Nie można zastąpić metody niewirtualnej. Nie można używać modyfikatora wirtualnego z modyfikatorami static, abstract, private lub override, MSDN .

Używanie nowego do cieniowania

Używasz nowego słowa kluczowego zamiast zastępowania, oto co robi new

  • Jeśli metoda w klasie pochodnej nie jest poprzedzona słowami kluczowymi new lub override, kompilator wygeneruje ostrzeżenie, a metoda będzie zachowywać się tak, jakby było obecne słowo kluczowe new.

  • Jeśli metoda w klasie pochodnej jest poprzedzona słowem kluczowym new, metoda jest definiowana jako niezależna od metody w klasie bazowej. Ten artykuł w witrynie MSDN wyjaśnia to bardzo dobrze.

Wiązanie wczesne VS Wiązanie późne

Mamy wczesne wiązanie w czasie kompilacji dla metody normalnej (nie wirtualnej), która jest obecnym przypadkiem, w którym kompilator połączy wywołanie metody klasy bazowej, która jest metodą typu referencyjnego (klasa bazowa) zamiast obiektu jest przechowywany w referencji bazy class, czyli obiekt klasy pochodnej . Dzieje się tak, ponieważ ShowInfonie jest to metoda wirtualna. Późne wiązanie jest wykonywane w czasie wykonywania dla (metoda wirtualna / nadpisana) przy użyciu tabeli metod wirtualnych (vtable).

W przypadku normalnej funkcji kompilator może obliczyć numeryczne położenie jej w pamięci. Następnie, gdy funkcja jest wywoływana, może wygenerować instrukcję wywołania funkcji pod tym adresem.

Dla obiektu, który ma jakiekolwiek metody wirtualne, kompilator wygeneruje tabelę v-table. Zasadniczo jest to tablica zawierająca adresy metod wirtualnych. Każdy obiekt, który ma metodę wirtualną, będzie zawierał ukryty element członkowski wygenerowany przez kompilator, który jest adresem tabeli v-table. Kiedy wywoływana jest funkcja wirtualna, kompilator ustali, jaka jest pozycja odpowiedniej metody w tabeli v. Następnie wygeneruje kod, który zajrzy w v-table obiektów i wywoła metodę wirtualną w tej pozycji, Reference .


7

Chcę wykorzystać odpowiedź Achratta . Aby uzyskać kompletność, różnica polega na tym, że OP oczekuje, że newsłowo kluczowe w metodzie klasy pochodnej zastąpi metodę klasy bazowej. Właściwie to się ukrywa metodę klasy bazowej.

W języku C #, jak wspomniano w innej odpowiedzi, zastępowanie tradycyjnej metody musi być jawne; metoda klasy bazowej musi być oznaczona jako, virtuala klasa pochodna musi być konkretnieoverride metodą klasy bazowej. Jeśli tak się stanie, nie ma znaczenia, czy obiekt jest traktowany jako instancja klasy bazowej czy pochodnej; metoda pochodna zostanie znaleziona i wywołana. Odbywa się to w podobny sposób jak w C ++; metoda oznaczona jako „wirtualna” lub „nadpisanie” podczas kompilacji jest rozwiązywana „późno” (w czasie wykonywania) przez określenie rzeczywistego typu obiektu, do którego się odwołuje, i przechodzenie hierarchii obiektów w dół wzdłuż drzewa od typu zmiennej do rzeczywistego typu obiektu, aby znaleźć najbardziej pochodną implementację metody zdefiniowanej przez typ zmiennej.

Różni się to od języka Java, który umożliwia „niejawne przesłonięcia”; na przykład metody (niestatyczne), po prostu zdefiniowanie metody o tej samej sygnaturze (nazwa i liczba / typ parametrów) spowoduje, że podklasa zastąpi nadklasę.

Ponieważ często warto rozszerzyć lub zastąpić funkcjonalność metody niewirtualnej, której nie kontrolujesz, C # zawiera również newkontekstowe słowo kluczowe. Słowo newkluczowe „ukrywa” metodę nadrzędną zamiast ją przesłonić. Każda dziedziczna metoda może zostać ukryta, niezależnie od tego, czy jest wirtualna, czy nie; pozwala to programistom na wykorzystanie członków, których chcesz odziedziczyć po rodzicach, bez konieczności obchodzenia się z tymi, których nie masz, a jednocześnie umożliwia prezentowanie tego samego „interfejsu” konsumentom Twojego kodu.

Ukrywanie działa podobnie do nadpisywania z perspektywy osoby korzystającej z Twojego obiektu na poziomie dziedziczenia, na którym zdefiniowana jest metoda ukrywania lub poniżej niego. Z przykładu pytania, programista tworzący Nauczyciela i przechowujący to odniesienie w zmiennej typu Nauczyciel zobaczy zachowanie implementacji ShowInfo () od Nauczyciela, która ukrywa to przed Person. Jednak osoba pracująca z Twoim obiektem w kolekcji rekordów Person (tak jak Ty) zobaczy zachowanie implementacji Person metody ShowInfo (); ponieważ metoda Nauczyciela nie przesłania swojego rodzica (co również wymagałoby wirtualizacji Person.ShowInfo ()), kod działający na poziomie abstrakcji Person nie znajdzie implementacji Nauczyciela i nie będzie jej używał.

Ponadto newsłowo kluczowe nie tylko zrobi to jawnie, ale C # umożliwia niejawne ukrywanie metod; po prostu zdefiniowanie metody z taką samą sygnaturą jak metoda klasy nadrzędnej, bez overridelub new, spowoduje jej ukrycie (chociaż spowoduje to ostrzeżenie kompilatora lub skargę od niektórych asystentów refaktoryzacji, takich jak ReSharper lub CodeRush). Jest to kompromis, jaki wymyślili projektanci C #, między jawnymi przesłonięciami w C ++ a niejawnymi przesłonięciami Javy, i chociaż jest elegancki, nie zawsze daje takie zachowanie, jakiego można by się spodziewać, jeśli pochodzisz z tła w którymkolwiek ze starszych języków.

Oto nowe rzeczy: staje się to skomplikowane, gdy połączysz dwa słowa kluczowe w długim łańcuchu dziedziczenia. Rozważ następujące:

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();

Wynik:

Foo
Bar
Baz
Bai
Bat
---
Bar
Bar
Bai
Bai
Bat
---
Bar
Bar
Bai
Bai
Bat

Można się spodziewać pierwszego zestawu pięciu; ponieważ każdy poziom ma implementację i odwołuje się do obiektu tego samego typu, do którego została utworzona instancja, środowisko wykonawcze rozpoznaje każde wywołanie poziomu dziedziczenia, do którego odwołuje się typ zmiennej.

Drugi zestaw pięciu jest wynikiem przypisania każdej instancji do zmiennej bezpośredniego typu nadrzędnego. Otóż, pewne różnice w zachowaniu się trzęsą; foo2, który w rzeczywistości jest Barrzutowaniem jako a Foo, nadal znajdzie bardziej pochodną metodę rzeczywistego typu obiektu Bar. bar2jest a Baz, ale w przeciwieństwie do with foo2, ponieważ Baz nie zastępuje jawnie implementacji Bara (nie może; Bar sealedit), nie jest widziany przez środowisko wykonawcze, gdy wygląda „z góry na dół”, więc zamiast tego wywoływana jest implementacja Bar. Zauważ, że Baz nie musi używać newsłowa kluczowego; otrzymasz ostrzeżenie kompilatora, jeśli pominiesz słowo kluczowe, ale niejawnym zachowaniem w C # jest ukrycie metody nadrzędnej. baz2jest a Bai, który zastępujeBaz snewimplementacja, więc jej zachowanie jest podobne do foo2's; wywoływana jest rzeczywista implementacja typu obiektu w Bai. bai2jest a Bat, który ponownie ukrywa Baiimplementację metody swojego rodzica i zachowuje się tak samo, bar2jakby implementacja Baia nie została zapieczętowana, więc teoretycznie Bat mógł przesłonić metodę zamiast ją ukryć. Wreszcie bat2jest to a Bak, które nie ma nadrzędnej implementacji żadnego rodzaju i po prostu używa implementacji swojego rodzica.

Trzeci zestaw pięciu ilustruje zachowanie pełnej rozdzielczości z góry na dół. Wszystko faktycznie odwołuje się do wystąpienia najbardziej pochodnej klasy w łańcuchu, Bakale rozpoznawanie na każdym poziomie typu zmiennej jest wykonywane przez rozpoczęcie od tego poziomu łańcucha dziedziczenia i drążenie w dół do najbardziej pochodnego jawnego zastąpienia metody, które są tych w Bar, Baii Bat. W ten sposób metoda ukrywania „przerywa” nadrzędny łańcuch dziedziczenia; musisz pracować z obiektem na poziomie dziedziczenia lub poniżej tego poziomu, który ukrywa metodę, aby można było użyć metody ukrywania. W przeciwnym razie metoda ukryta jest „odkrywana” i używana zamiast niej.


4

Przeczytaj o polimorfizmie w C #: Polimorphism (Przewodnik programowania w języku C #)

Oto przykład stamtąd:

Gdy używane jest słowo kluczowe new, nowe elementy członkowskie klasy są wywoływane zamiast zastępowanych elementów członkowskich klasy bazowej. Te elementy członkowskie klasy bazowej nazywane są ukrytymi członkami. Ukrytych członków klasy można nadal wywoływać, jeśli wystąpienie klasy pochodnej jest rzutowane na wystąpienie klasy bazowej. Na przykład:

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

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

3

Musisz to zrobić, virtuala następnie zastąpić tę funkcję w Teacher. Ponieważ dziedziczysz i używasz wskaźnika podstawowego do odwoływania się do klasy pochodnej, musisz zastąpić go za pomocą virtual. newsłuży do ukrywania basemetody klasy w odwołaniu do klasy pochodnej, a nie w baseodwołaniu do klasy.


3

Chciałbym dodać kilka więcej przykładów, aby rozwinąć informacje na ten temat. Mam nadzieję, że to też pomoże:

Oto przykład kodu, który oczyszcza powietrze wokół tego, co się dzieje, gdy typ pochodny zostanie przypisany do typu podstawowego. Jakie metody są dostępne i jaka jest różnica między metodami nadpisanymi i ukrytymi w tym kontekście.

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

Inną małą anomalią jest to, że dla następującego wiersza kodu:

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

Kompilator VS (intellisense) pokaże a.foo () jako A.foo ().

W związku z tym jasne jest, że gdy typ bardziej pochodny jest przypisany do typu podstawowego, zmienna „typ podstawowy” działa jako typ podstawowy do momentu odwołania do metody, która jest zastępowana w typie pochodnym. Może to stać się trochę sprzeczne z intuicją w przypadku ukrytych metod lub metod o tej samej nazwie (ale nie nadpisanej) między typami rodzica i dziecka.

Ten przykładowy kod powinien pomóc w określeniu tych zastrzeżeń!


2

C # różni się od języka Java w zachowaniu przesłaniania klasy nadrzędnej / podrzędnej. Domyślnie w Javie wszystkie metody są wirtualne, więc pożądane zachowanie jest obsługiwane po wyjęciu z pudełka.

W C # musisz oznaczyć metodę jako wirtualną w klasie bazowej, wtedy otrzymasz to, czego chcesz.


2

Nowy kluczowe stwierdzić, że metoda w obecnej klasie będzie tylko praca, jeśli masz instancję Wychowawca przechowywane w zmiennej typu Nauczyciela. Lub możesz go wywołać za pomocą rzutowania: ((Nauczyciel) Osoba) .ShowInfo ()


1

Typ zmiennej „nauczyciel” jest tutaj typeof(Person)i ten typ nic nie wie o klasie nauczyciela i nie próbuje szukać żadnych metod w typach pochodnych. Aby wywołać metodę Wychowawca oddasz zmienną: (person as Teacher).ShowInfo().

Aby wywołać określoną metodę opartą na typie wartości, należy użyć słowa kluczowego „virtual” w swojej klasie bazowej i przesłonić metody wirtualne w klasach pochodnych. Takie podejście pozwala na implementację klas pochodnych z lub bez zastępowania metod wirtualnych. Metody klasy bazowej będą wywoływane dla typów bez overided virtuals.

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

Może być za późno ... Ale pytanie jest proste, a odpowiedź powinna mieć ten sam poziom złożoności.

W twojej zmiennej kodu osoba nie wie nic o Teacher.ShowInfo (). Nie ma możliwości wywołania ostatniej metody z odwołania do klasy bazowej, ponieważ nie jest ona wirtualna.

Istnieje przydatne podejście do dziedziczenia - spróbuj wyobrazić sobie, co chcesz powiedzieć o hierarchii kodu. Spróbuj także wyobrazić sobie, co mówi o sobie jedno lub drugie narzędzie. Np. Jeśli dodasz funkcję wirtualną do klasy bazowej, przypuszczasz: 1. może mieć domyślną implementację; 2. może zostać ponownie zaimplementowany w klasie pochodnej. Dodanie funkcji abstrakcyjnej oznacza tylko jedno - podklasa musi stworzyć implementację. Ale jeśli masz zwykłą funkcję - nie oczekujesz, że ktoś zmieni jej implementację.


0

Kompilator robi to, ponieważ nie wie, że jest to plik Teacher. Wie tylko, że jest to Personcoś, co się z niego wywodzi. Więc wszystko, co może zrobić, to wywołać Person.ShowInfo()metodę.


0

Chciałem tylko udzielić krótkiej odpowiedzi -

Należy używać virtuali overridew klasach, które można przesłonić. Użyj virtualdla metod, które mogą być przesłonięte przez klasy potomne i użyj overridedla metod, które powinny przesłonić takie virtualmetody.


0

Napisałem ten sam kod, o którym wspomniałeś powyżej w Javie, z wyjątkiem pewnych zmian i działał dobrze, z wyjątkiem. Metoda klasy bazowej jest nadpisywana i wyświetlany jest komunikat „Jestem nauczycielem”.

Powód: ponieważ tworzymy odwołanie do klasy bazowej (która może mieć odwołanie do instancji klasy pochodnej), która w rzeczywistości zawiera odwołanie do klasy pochodnej. A ponieważ wiemy, że instancja zawsze najpierw patrzy na swoje metody, jeśli ją tam znajdzie, wykonuje ją, a jeśli nie znajduje tam definicji, przechodzi w górę w hierarchii.

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

Opierając się na doskonałej demonstracji Keitha S. i odpowiedziach wszystkich innych, a ze względu na kompletną kompletność, wrzućmy jawne implementacje interfejsu, aby pokazać, jak to działa. Rozważ poniższe kwestie:

przestrzeń nazw 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());
    }
}

}

Oto wynik:

osoba: Jestem Person == LinqConsoleApp.Teacher

nauczyciel: jestem nauczycielem == LinqConsoleApp.Teacher

person1: Jestem nauczycielem == LinqConsoleApp.Teacher

person2: Jestem nauczycielem == LinqConsoleApp.Teacher

nauczyciel1: Jestem nauczycielem == LinqConsoleApp.Teacher

person4: Jestem Person == LinqConsoleApp.Person

person3: Jestem interfejsem Person == LinqConsoleApp.Person

Dwie rzeczy do zapamiętania:
Metoda Teacher.ShowInfo () pomija słowo kluczowe new. W przypadku pominięcia new zachowanie metody jest takie samo, jak w przypadku jawnego zdefiniowania słowa kluczowego new.

Słowa kluczowego override można używać tylko w połączeniu z wirtualnym słowem kluczowym. Metoda klasy bazowej musi być wirtualna. Lub abstrakcyjny, w którym to przypadku klasa musi być również abstrakcyjna.

person pobiera podstawową implementację ShowInfo, ponieważ klasa Teacher nie może przesłonić podstawowej implementacji (bez deklaracji wirtualnej), a person to .GetType (Teacher), więc ukrywa implementację klasy Teacher.

nauczyciel pobiera pochodną implementację narzędzia ShowInfo dla nauczyciela, ponieważ nauczyciel, ponieważ jest to typ (Nauczyciel), a nie jest na poziomie dziedziczenia Osoba.

person1 pobiera pochodną implementację Teacher, ponieważ jest to .GetType (Teacher), a implikowane nowe słowo kluczowe ukrywa podstawową implementację.

person2 pobiera również pochodną implementację Nauczyciela, mimo że implementuje IPerson i otrzymuje jawne rzutowanie na IPerson. Dzieje się tak ponownie, ponieważ klasa Teacher nie implementuje jawnie metody IPerson.ShowInfo ().

Teacher1 również pobiera pochodną implementację Teacher, ponieważ jest to .GetType (Teacher).

Tylko person3 pobiera implementację IPerson ShowInfo, ponieważ tylko klasa Person jawnie implementuje metodę, a person3 jest instancją typu IPerson.

Aby jawnie zaimplementować interfejs, należy zadeklarować instancję var typu docelowego interfejsu, a klasa musi jawnie implementować (w pełni kwalifikować) elementy składowe interfejsu.

Zauważ, że nawet person4 nie otrzymuje implementacji IPerson.ShowInfo. Dzieje się tak, ponieważ mimo że person4 to .GetType (Person) i mimo że Person implementuje IPerson, person4 nie jest instancją IPerson.


Widzę, że prawidłowe formatowanie kodu stanowi pewne wyzwanie. Nie ma czasu, żeby to teraz
upiększyć

0

Próbka LinQPad, aby uruchomić się na ślepo i zredukować powielanie kodu. Myślę, że to jest to, co próbujesz zrobić.

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();
    }
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.