Mengapa memanggil metode di kelas turunan saya memanggil metode kelas dasar?


146

Pertimbangkan kode ini:

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

Ketika saya menjalankan kode ini, berikut ini dihasilkan:

Saya orang

Namun, Anda dapat melihat bahwa ini adalah turunan dari Teacher, bukan dari Person. Mengapa kode melakukan itu?


3
Pertanyaan dari orang Java: adalah Console.ReadLine (); perlu untuk contoh ini?
Kaya

2
@Shahrooz Saya tidak bisa menjawab pertanyaan Anda - Saya tidak tahu C #. Saya mengajukan pertanyaan C # yang sangat sepele, yaitu apakah panggilan ke ReadLine dalam metode utama diperlukan agar dapat memanggil WriteLine di kelas Person dan Teacher.
Kaya

6
Ya, .Net secara otomatis menutup jendela konsol ketika Main () keluar. untuk menyiasatinya, kami menggunakan Console.Read () atau Console.Readline () untuk menunggu input tambahan sehingga konsol tetap terbuka.
Kapten Kenpachi

15
@Kaya tidak, itu tidak perlu , tetapi Anda akan sering melihatnya karena alasan ini: ketika menjalankan program konsol dari Visual Studio, pada penghentian program jendela perintah langsung ditutup, jadi jika Anda ingin melihat output program, Anda perlu memberitahukannya menunggu.
AakashM

1
@AashashM Terima kasih - Saya menghabiskan waktu saya di Eclipse di mana konsol adalah bagian dari jendela Eclipse, dan karenanya tidak menutup. Itu masuk akal.
Kaya

Jawaban:


368

Ada perbedaan antara newdan virtual/ override.

Anda dapat membayangkan, bahwa suatu kelas, ketika dipakai, tidak lebih dari sebuah tabel petunjuk, menunjuk pada implementasi aktual dari metodenya. Gambar berikut harus memvisualisasikan ini dengan cukup baik:

Ilustrasi implementasi metode

Sekarang ada berbagai cara, suatu metode dapat didefinisikan. Masing-masing berperilaku berbeda ketika digunakan dengan warisan. Cara standar selalu berfungsi seperti gambar di atas. Jika Anda ingin mengubah perilaku ini, Anda dapat melampirkan kata kunci yang berbeda ke metode Anda.

1. Kelas abstrak

Yang pertama adalah abstract. abstractmetode hanya menunjuk ke tempat:

Ilustrasi kelas abstrak

Jika kelas Anda berisi anggota abstrak, itu juga harus ditandai sebagai abstract, jika tidak kompilator tidak akan mengkompilasi aplikasi Anda. Anda tidak bisa membuat instance abstractkelas, tetapi Anda bisa mewarisi dari mereka dan membuat instance dari kelas yang diwarisi dan mengaksesnya menggunakan definisi kelas dasar. Dalam contoh Anda ini akan terlihat seperti:

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

Jika dipanggil, perilaku ShowInfobervariasi, berdasarkan pada implementasi:

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

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

Keduanya, Students dan Teachers adalah Persons, tetapi mereka berperilaku berbeda ketika mereka diminta untuk meminta informasi tentang diri mereka sendiri. Namun, cara untuk meminta mereka meminta informasi mereka, adalah sama: Menggunakan Personantarmuka kelas.

Jadi apa yang terjadi di balik layar, ketika Anda mewarisi dari Person? Saat menerapkan ShowInfo, pointer tidak menunjuk ke mana- mana lagi, sekarang menunjuk ke implementasi yang sebenarnya! Saat membuat sebuah Studentinstance, itu menunjuk ke Students ShowInfo:

Ilustrasi metode yang diwariskan

2. Metode virtual

Cara kedua adalah menggunakan virtualmetode. Perilaku ini sama, kecuali Anda memberikan implementasi standar opsional di kelas dasar Anda. Kelas dengan virtualanggota dapat di-instanciated, namun kelas yang diwariskan dapat memberikan implementasi yang berbeda. Inilah kode yang seharusnya terlihat berfungsi:

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

Perbedaan utama adalah, bahwa anggota pangkalan Person.ShowInfotidak menunjuk ke mana- mana lagi. Ini juga alasannya, mengapa Anda dapat membuat instance Person(dan karenanya tidak perlu ditandai abstractlagi):

Ilustrasi anggota virtual di dalam kelas dasar

Anda harus perhatikan, bahwa ini tidak terlihat berbeda dari gambar pertama untuk saat ini. Ini karena virtualmetode ini menunjuk ke implementasi " cara standar ". Menggunakan virtual, Anda dapat mengatakan Persons, bahwa mereka dapat (tidak harus ) memberikan implementasi yang berbeda untuk ShowInfo. Jika Anda memberikan implementasi yang berbeda (menggunakan override), seperti yang saya lakukan untuk di Teacheratas, gambar akan terlihat sama seperti untuk abstract. Bayangkan, kami tidak menyediakan implementasi khusus untuk Students:

public class Student : Person
{
}

Kode akan dipanggil seperti ini:

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

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

Dan gambar untuk Studentakan terlihat seperti ini:

Ilustrasi penerapan metode standar, menggunakan kata kunci virtual

3. Kata kunci ajaib `baru` alias" Membayangi "

newlebih hack di sekitar ini. Anda dapat memberikan metode dalam kelas umum, yang memiliki nama yang sama dengan metode di kelas dasar / antarmuka. Keduanya menunjuk pada implementasi kustom mereka sendiri:

Ilustrasi "jalan memutar" menggunakan kata kunci baru

Implementasinya terlihat seperti yang Anda berikan. Perilaku berbeda, berdasarkan cara Anda mengakses metode:

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

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

Perilaku ini bisa diinginkan, tetapi dalam kasus Anda itu menyesatkan.

Saya harap ini membuat hal-hal lebih jelas untuk dipahami untuk Anda!


9
Terima kasih atas jawaban Anda yang luar biasa

6
Apa yang Anda gunakan untuk membuat diagram itu?
BlueRaja - Danny Pflughoeft

2
Jawaban yang sangat bagus dan sangat teliti.
Nik Bougalis

8
tl; dr yang Anda gunakan newyang memecah fungsi warisan dan membuat fungsi baru terpisah dari fungsi superclass '
ratchet freak

3
@ Tommon: Sebenarnya tidak ... Saya hanya ingin memperjelas bahwa panggilan itu bertentangan Person, bukan Student;)
Carsten

45

Subtipe polimorfisme dalam C # menggunakan virtualitas eksplisit, mirip dengan C ++ tetapi tidak seperti Java. Ini berarti bahwa Anda secara eksplisit harus menandai metode sebagai dapat ditimpa (yaitu virtual). Dalam C # Anda juga harus secara eksplisit menandai metode overriding sebagai menimpa (yaitu override) untuk mencegah kesalahan ketik.

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

Dalam kode dalam pertanyaan Anda, Anda menggunakan new, yang tidak berbayang alih-alih menimpa. Membayangi hanya mempengaruhi semantik waktu kompilasi daripada semantik runtime, karenanya output yang tidak diinginkan.


4
Siapa bilang OP tahu apa artinya itu.
Cole Johnson

@ColeJohnson saya akan menambahkan klarifikasi.

25

Anda harus membuat metode virtual dan Anda harus menimpa fungsi di kelas anak, untuk memanggil metode objek kelas yang Anda masukkan ke dalam referensi kelas induk.

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

Metode Virtual

Ketika metode virtual dipanggil, tipe run-time dari objek diperiksa untuk anggota utama. Anggota utama dalam kelas yang paling diturunkan disebut, yang mungkin anggota asli, jika tidak ada kelas turunan yang menimpa anggota. Secara default, metode non-virtual. Anda tidak dapat mengganti metode non-virtual. Anda tidak dapat menggunakan pengubah virtual dengan pengubah statis, abstrak, pribadi atau mengesampingkan, MSDN .

Menggunakan Baru untuk Membayangi

Anda menggunakan kata kunci baru alih-alih menimpa, inilah yang dilakukan baru

  • Jika metode dalam kelas turunan tidak didahului oleh kata kunci baru atau override, kompiler akan mengeluarkan peringatan dan metode akan berperilaku seolah-olah kata kunci baru hadir.

  • Jika metode dalam kelas turunan didahului dengan kata kunci baru, metode ini didefinisikan sebagai independen dari metode di kelas dasar , Artikel MSDN ini menjelaskan dengan sangat baik.

Mengikat awal VS Mengikat lebih lanjut

Kami memiliki ikatan awal pada waktu kompilasi untuk metode normal (bukan virtual) yang merupakan kasus saat ini kompiler akan mengikat panggilan ke metode kelas dasar yaitu metode tipe referensi (kelas dasar) dan bukan objek yang disimpan dalam referensi pangkalan kelas yaitu objek kelas turunan . Ini karena ShowInfoini bukan metode virtual. Mengikat terlambat dilakukan pada saat runtime untuk (metode virtual / override) menggunakan tabel metode virtual (vtable).

Untuk fungsi normal, kompiler dapat menghitung lokasi numeriknya dalam memori. Maka ketika fungsi dipanggil itu dapat menghasilkan instruksi untuk memanggil fungsi di alamat ini.

Untuk objek yang memiliki metode virtual, kompiler akan menghasilkan tabel-v. Ini pada dasarnya adalah array yang berisi alamat metode virtual. Setiap objek yang memiliki metode virtual akan berisi anggota tersembunyi yang dihasilkan oleh kompiler yang merupakan alamat dari v-table. Ketika fungsi virtual dipanggil, kompiler akan mencari tahu apa posisi metode yang sesuai di tabel-v. Ini kemudian akan menghasilkan kode untuk melihat objek-objek v-table dan memanggil metode virtual pada posisi ini, Referensi .


7

Saya ingin membangun dari jawaban Achratt . Untuk kelengkapan, perbedaannya adalah bahwa OP mengharapkan newkata kunci dalam metode kelas turunan untuk mengganti metode kelas dasar. Apa yang sebenarnya dilakukannya adalah menyembunyikan metode kelas dasar.

Dalam C #, seperti jawaban lain yang disebutkan, metode tradisional utama harus eksplisit; metode kelas dasar harus ditandai sebagai virtualdan kelas turunan harus secara khusus overridemetode kelas dasar. Jika ini dilakukan, maka tidak masalah apakah objek diperlakukan sebagai turunan dari kelas dasar atau kelas turunan; metode turunan ditemukan dan dipanggil. Ini dilakukan dengan cara yang sama seperti di C ++; metode bertanda "virtual" atau "menimpa", ketika dikompilasi, diselesaikan "terlambat" (saat runtime) dengan menentukan tipe aktual objek yang direferensikan, dan melintasi hierarki objek ke bawah sepanjang pohon dari tipe variabel ke tipe objek aktual, untuk menemukan implementasi metode yang paling banyak ditentukan oleh tipe variabel.

Ini berbeda dari Java, yang memungkinkan "override implisit"; misalnya metode (non-statis), cukup mendefinisikan metode dengan tanda tangan yang sama (nama dan nomor / jenis parameter) akan menyebabkan subclass menimpa superclass.

Karena sering bermanfaat untuk memperluas atau mengganti fungsi metode non-virtual yang tidak Anda kendalikan, C # juga menyertakan newkata kunci kontekstual. Kata newkunci "menyembunyikan" metode induk alih-alih menimpanya. Metode apa pun yang dapat diwariskan dapat disembunyikan apakah itu virtual atau tidak; ini memungkinkan Anda, pengembang, untuk meningkatkan anggota yang ingin Anda warisi dari orangtua, tanpa harus bekerja di sekitar yang tidak Anda miliki, sambil tetap memungkinkan Anda untuk menghadirkan "antarmuka" yang sama kepada konsumen kode Anda.

Menyembunyikan bekerja sama dengan mengesampingkan dari perspektif seseorang menggunakan objek Anda pada atau di bawah tingkat warisan di mana metode persembunyian didefinisikan. Dari contoh pertanyaan, seorang pembuat kode menciptakan seorang Guru dan menyimpan referensi itu dalam variabel dari tipe Guru akan melihat perilaku implementasi ShowInfo () dari Guru, yang menyembunyikan satu dari Orang. Namun, seseorang yang bekerja dengan objek Anda dalam kumpulan catatan Orang (seperti Anda) akan melihat perilaku penerapan Orang pada ShowInfo (); karena metode Guru tidak mengesampingkan orang tuanya (yang juga akan membutuhkan Person.ShowInfo () menjadi virtual), kode yang bekerja pada tingkat abstraksi Orang tidak akan menemukan implementasi Guru dan tidak akan menggunakannya.

Selain itu, newkata kunci tidak hanya akan melakukan ini secara eksplisit, C # memungkinkan penyembunyian metode implisit; cukup mendefinisikan metode dengan tanda tangan yang sama dengan metode kelas induk, tanpa overrideatau new, akan menyembunyikannya (meskipun akan menghasilkan peringatan kompiler atau keluhan dari asisten refactoring tertentu seperti ReSharper atau CodeRush). Ini adalah kompromi yang dibuat oleh desainer C # antara override eksplisit C ++ vs yang implisit Java, dan meskipun elegan, itu tidak selalu menghasilkan perilaku yang Anda harapkan jika Anda datang dari latar belakang dalam salah satu bahasa yang lebih tua.

Inilah hal-hal baru: Ini menjadi rumit ketika Anda menggabungkan dua kata kunci dalam rantai pewarisan panjang. Pertimbangkan yang berikut ini:

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

Keluaran:

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

Set pertama terdiri dari lima yang diharapkan; karena setiap level memiliki implementasi, dan direferensikan sebagai objek dengan tipe yang sama seperti yang dipakai, runtime menyelesaikan setiap panggilan ke level pewarisan yang dirujuk oleh tipe variabel.

Set kedua dari lima adalah hasil dari menetapkan setiap instance ke variabel dari tipe induk langsung. Sekarang, beberapa perbedaan dalam perilaku mengguncang; foo2, yang sebenarnya Bardilemparkan sebagai Foo, masih akan menemukan metode yang lebih diturunkan dari tipe objek aktual Bar. bar2adalah Baz, tetapi tidak seperti dengan foo2, karena Baz tidak secara eksplisit mengesampingkan implementasi Bar (itu tidak bisa; Bar sealeditu), itu tidak terlihat oleh runtime ketika mencari "top-down", sehingga implementasi Bar disebut sebagai gantinya. Perhatikan bahwa Baz tidak harus menggunakan newkata kunci; Anda akan mendapatkan peringatan kompiler jika Anda menghilangkan kata kunci, tetapi perilaku tersirat dalam C # adalah menyembunyikan metode induk. baz2adalah Baiyang menimpa Baz'snewimplementasi, sehingga perilakunya mirip dengan foo2; implementasi tipe objek aktual di Bai disebut. bai2adalah Bat, yang lagi-lagi menyembunyikan Baiimplementasi metode orang tuanya , dan berperilaku sama seperti bar2meskipun implementasi Bai tidak disegel, jadi secara teoritis Bat bisa menimpa alih-alih menyembunyikan metode tersebut. Akhirnya, bat2adalah Bak, yang tidak memiliki implementasi utama dari kedua jenis, dan hanya menggunakan orang tuanya.

Set ketiga dari lima menggambarkan perilaku resolusi top-down penuh. Semuanya sebenarnya merujuk contoh dari kelas yang paling diturunkan dalam rantai, Baktetapi resolusi pada setiap tingkat tipe variabel dilakukan dengan mulai dari tingkat rantai pewarisan dan menelusuri hingga menimpa eksplisit metode yang paling diturunkan , yaitu pada mereka Bar, Baidan Bat. Metode menyembunyikan demikian "memutus" rantai warisan yang utama; Anda harus bekerja dengan objek pada atau di bawah tingkat warisan yang menyembunyikan metode agar metode persembunyian yang akan digunakan. Jika tidak, metode tersembunyi "terbuka" dan digunakan sebagai gantinya.


4

Silakan baca tentang polimorfisme dalam C #: Polimorfisme (Panduan Pemrograman C #)

Ini adalah contoh dari sana:

Ketika kata kunci baru digunakan, anggota kelas baru dipanggil dan bukan anggota kelas dasar yang telah diganti. Anggota kelas dasar itu disebut anggota tersembunyi. Anggota kelas tersembunyi masih bisa dipanggil jika turunan dari kelas turunan dilemparkan ke turunan dari kelas dasar. Sebagai contoh:

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

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

3

Anda harus membuatnya virtuallalu menimpa fungsi itu di Teacher. Saat Anda mewarisi dan menggunakan pointer basis untuk merujuk ke kelas turunan, Anda perlu menimpanya menggunakan virtual. newadalah untuk menyembunyikan basemetode kelas pada referensi kelas turunan dan bukan basereferensi kelas.


3

Saya ingin menambahkan beberapa contoh lagi untuk memperluas info tentang ini. Semoga ini juga membantu:

Berikut adalah contoh kode yang membersihkan udara di sekitar apa yang terjadi ketika jenis turunan ditugaskan ke jenis pangkalan. Metode mana yang tersedia dan perbedaan antara metode yang diganti dan tersembunyi dalam konteks ini.

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

Anomali kecil lain adalah untuk baris kode berikut:

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

Kompiler VS (intellisense) akan menampilkan a.foo () sebagai A.foo ().

Oleh karena itu, jelas bahwa ketika tipe yang lebih diturunkan ditugaskan ke tipe basis, variabel 'tipe dasar' bertindak sebagai tipe dasar hingga metode yang ditimpa dalam tipe turunan direferensikan. Ini mungkin menjadi sedikit kontra-intuitif dengan metode tersembunyi atau metode dengan nama yang sama (tetapi tidak diganti) antara tipe induk dan anak.

Contoh kode ini akan membantu menggambarkan peringatan ini!


2

C # berbeda dengan java dalam perilaku override kelas induk / anak. Secara default di Java semua metode adalah virtual, sehingga perilaku yang Anda inginkan didukung di luar kotak.

Di C # Anda harus menandai metode sebagai virtual di kelas dasar, maka Anda akan mendapatkan apa yang Anda inginkan.


2

Kata kunci baru mengatakan bahwa metode di kelas saat ini hanya akan berfungsi jika Anda memiliki instance dari kelas Guru yang disimpan dalam variabel tipe Guru. Atau Anda dapat memicu menggunakan coran: ((Guru) Orang) .ShowInfo ()


1

Tipe variabel 'guru' di sini adalah typeof(Person)dan tipe ini tidak tahu apa-apa tentang kelas guru dan tidak mencoba mencari metode apa pun dalam tipe turunan. Untuk memanggil metode kelas Guru Anda harus membuang variabel Anda: (person as Teacher).ShowInfo().

Untuk memanggil metode spesifik berdasarkan tipe nilai Anda harus menggunakan kata kunci 'virtual' di kelas dasar Anda dan menimpa metode virtual di kelas turunan. Pendekatan ini memungkinkan untuk mengimplementasikan kelas turunan dengan atau tanpa mengesampingkan metode virtual. Metode kelas dasar akan dipanggil untuk jenis tanpa virtual overided.

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

Mungkin sudah terlambat ... Tetapi pertanyaannya sederhana dan jawabannya harus memiliki tingkat kompleksitas yang sama.

Dalam variabel kode Anda, orang tidak tahu apa-apa tentang Teacher.ShowInfo (). Tidak ada cara untuk memanggil metode terakhir dari referensi kelas dasar, karena itu bukan virtual.

Ada pendekatan yang bermanfaat untuk pewarisan - coba bayangkan apa yang ingin Anda katakan dengan hierarki kode Anda. Coba juga bayangkan apa yang dikatakan oleh satu atau alat lain tentang dirinya sendiri. Misalnya, jika Anda menambahkan fungsi virtual ke dalam kelas dasar yang Anda duga: 1. dapat memiliki implementasi default; 2. mungkin diterapkan kembali di kelas turunan. Jika Anda menambahkan fungsi abstrak, artinya hanya satu hal - subclass harus membuat implementasi. Tetapi jika Anda memiliki fungsi sederhana - Anda tidak mengharapkan siapa pun untuk mengubah implementasinya.


0

Compiler melakukan ini karena tidak tahu itu adalah a Teacher. Yang ia tahu adalah bahwa itu adalah Personatau sesuatu yang berasal darinya. Jadi yang bisa dilakukan hanyalah memanggil Person.ShowInfo()metode.


0

Hanya ingin memberikan jawaban singkat -

Anda harus menggunakan virtualdan overridedi kelas yang bisa diganti. Gunakan virtualuntuk metode yang dapat ditimpa oleh kelas anak dan gunakan overrideuntuk metode yang harus mengganti virtualmetode tersebut.


0

Saya menulis kode yang sama seperti yang Anda sebutkan di atas di java kecuali beberapa perubahan dan itu berfungsi dengan baik seperti yang dikecualikan. Metode kelas dasar ditimpa dan output yang ditampilkan adalah "Saya Guru".

Alasan: Saat kami membuat referensi kelas dasar (yang mampu merujuk contoh kelas turunan) yang sebenarnya mengandung referensi kelas turunan. Dan seperti yang kita ketahui bahwa instance selalu melihat metodenya terlebih dahulu jika menemukannya di sana ia mengeksekusi, dan jika tidak menemukan definisi di sana ia naik dalam hierarki.

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

Membangun demonstrasi Keith S. yang sangat baik dan jawaban kualitas setiap orang lain dan demi kelengkapan uber mari kita lanjutkan dan melemparkan implementasi antarmuka eksplisit untuk mendemonstrasikan cara kerjanya. Pertimbangkan di bawah ini:

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

}

Inilah hasilnya:

orang: Saya Orang == LinqConsoleApp.Teacher

guru: Saya Guru == LinqConsoleApp.Teacher

person1: Saya Guru == LinqConsoleApp.Teacher

person2: Saya Guru == LinqConsoleApp.Teacher

teacher1: Saya Guru == LinqConsoleApp.Teacher

person4: Saya Person == LinqConsoleApp.Person

person3: Saya antarmuka Person == LinqConsoleApp.Person

Dua hal yang perlu diperhatikan:
Metode Teacher.ShowInfo () menghilangkan kata kunci baru. Ketika baru dihilangkan, perilaku metode sama dengan jika kata kunci baru didefinisikan secara eksplisit.

Anda hanya dapat menggunakan kata kunci override bersama dengan kata kunci virtual. Metode kelas dasar harus virtual. Atau abstrak yang dalam hal ini kelasnya juga harus abstrak.

seseorang mendapatkan implementasi basis dari ShowInfo karena kelas Guru tidak dapat mengesampingkan implementasi basis (tidak ada deklarasi virtual) dan orang tersebut .GetType (Guru) sehingga menyembunyikan implementasi kelas Guru.

guru mendapatkan implementasi Guru yang diturunkan dari ShowInfo karena guru karena itu Typeof (Guru) dan itu bukan pada tingkat warisan Orang.

person1 mendapatkan implementasi Guru yang diturunkan karena .GetType (Guru) dan kata kunci baru yang tersirat menyembunyikan implementasi dasar.

person2 juga mendapatkan implementasi Guru yang diturunkan meskipun menerapkan IPerson dan mendapat peran eksplisit untuk IPerson. Ini lagi karena kelas guru tidak secara eksplisit menerapkan metode IPerson.ShowInfo ().

teacher1 juga mendapatkan implementasi Guru turunan karena .GetType (Guru).

Hanya person3 yang mendapatkan implementasi IPerson dari ShowInfo karena hanya kelas Person yang mengimplementasikan metode secara eksplisit dan person3 adalah turunan dari tipe IPerson.

Untuk mengimplementasikan antarmuka secara eksplisit, Anda harus mendeklarasikan instance var dari tipe antarmuka target dan sebuah kelas harus secara eksplisit mengimplementasikan (sepenuhnya memenuhi syarat) anggota antarmuka.

Perhatikan bahkan orang 4 tidak mendapatkan implementasi IPerson.ShowInfo. Ini karena meskipun person4 adalah .GetType (Person) dan meskipun Person mengimplementasikan IPerson, person4 bukanlah turunan dari IPerson.


Saya melihat memformat kode dengan benar menghadirkan sedikit tantangan. Tidak ada waktu untuk melakukannya sekarang ...
steely

0

Contoh LinQPad untuk memulai secara membabi buta dan mengurangi duplikasi kode yang saya pikir adalah apa yang Anda coba lakukan.

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();
    }
}
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.