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?
c#
class
derived-class
kebiruan
sumber
sumber
Jawaban:
Ada perbedaan antara
new
danvirtual
/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:
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
.abstract
metode hanya menunjuk ke tempat:Jika kelas Anda berisi anggota abstrak, itu juga harus ditandai sebagai
abstract
, jika tidak kompilator tidak akan mengkompilasi aplikasi Anda. Anda tidak bisa membuat instanceabstract
kelas, 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:Jika dipanggil, perilaku
ShowInfo
bervariasi, berdasarkan pada implementasi:Keduanya,
Student
s danTeacher
s adalahPerson
s, tetapi mereka berperilaku berbeda ketika mereka diminta untuk meminta informasi tentang diri mereka sendiri. Namun, cara untuk meminta mereka meminta informasi mereka, adalah sama: MenggunakanPerson
antarmuka kelas.Jadi apa yang terjadi di balik layar, ketika Anda mewarisi dari
Person
? Saat menerapkanShowInfo
, pointer tidak menunjuk ke mana- mana lagi, sekarang menunjuk ke implementasi yang sebenarnya! Saat membuat sebuahStudent
instance, itu menunjuk keStudent
sShowInfo
:2. Metode virtual
Cara kedua adalah menggunakan
virtual
metode. Perilaku ini sama, kecuali Anda memberikan implementasi standar opsional di kelas dasar Anda. Kelas denganvirtual
anggota dapat di-instanciated, namun kelas yang diwariskan dapat memberikan implementasi yang berbeda. Inilah kode yang seharusnya terlihat berfungsi:Perbedaan utama adalah, bahwa anggota pangkalan
Person.ShowInfo
tidak menunjuk ke mana- mana lagi. Ini juga alasannya, mengapa Anda dapat membuat instancePerson
(dan karenanya tidak perlu ditandaiabstract
lagi):Anda harus perhatikan, bahwa ini tidak terlihat berbeda dari gambar pertama untuk saat ini. Ini karena
virtual
metode ini menunjuk ke implementasi " cara standar ". Menggunakanvirtual
, Anda dapat mengatakanPersons
, bahwa mereka dapat (tidak harus ) memberikan implementasi yang berbeda untukShowInfo
. Jika Anda memberikan implementasi yang berbeda (menggunakanoverride
), seperti yang saya lakukan untuk diTeacher
atas, gambar akan terlihat sama seperti untukabstract
. Bayangkan, kami tidak menyediakan implementasi khusus untukStudent
s:Kode akan dipanggil seperti ini:
Dan gambar untuk
Student
akan terlihat seperti ini:3. Kata kunci ajaib `baru` alias" Membayangi "
new
lebih 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:Implementasinya terlihat seperti yang Anda berikan. Perilaku berbeda, berdasarkan cara Anda mengakses metode:
Perilaku ini bisa diinginkan, tetapi dalam kasus Anda itu menyesatkan.
Saya harap ini membuat hal-hal lebih jelas untuk dipahami untuk Anda!
sumber
new
yang memecah fungsi warisan dan membuat fungsi baru terpisah dari fungsi superclass 'Person
, bukanStudent
;)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 (yaituoverride
) untuk mencegah kesalahan ketik.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.sumber
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.
Metode Virtual
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
ShowInfo
ini bukan metode virtual. Mengikat terlambat dilakukan pada saat runtime untuk (metode virtual / override) menggunakan tabel metode virtual (vtable).sumber
Saya ingin membangun dari jawaban Achratt . Untuk kelengkapan, perbedaannya adalah bahwa OP mengharapkan
new
kata 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
virtual
dan kelas turunan harus secara khususoverride
metode 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
new
kata kunci kontekstual. Katanew
kunci "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,
new
kata 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, tanpaoverride
ataunew
, 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:
Keluaran:
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 sebenarnyaBar
dilemparkan sebagaiFoo
, masih akan menemukan metode yang lebih diturunkan dari tipe objek aktual Bar.bar2
adalahBaz
, tetapi tidak seperti denganfoo2
, karena Baz tidak secara eksplisit mengesampingkan implementasi Bar (itu tidak bisa; Barsealed
itu), itu tidak terlihat oleh runtime ketika mencari "top-down", sehingga implementasi Bar disebut sebagai gantinya. Perhatikan bahwa Baz tidak harus menggunakannew
kata kunci; Anda akan mendapatkan peringatan kompiler jika Anda menghilangkan kata kunci, tetapi perilaku tersirat dalam C # adalah menyembunyikan metode induk.baz2
adalahBai
yang menimpaBaz
'snew
implementasi, sehingga perilakunya mirip denganfoo2
; implementasi tipe objek aktual di Bai disebut.bai2
adalahBat
, yang lagi-lagi menyembunyikanBai
implementasi metode orang tuanya , dan berperilaku sama sepertibar2
meskipun implementasi Bai tidak disegel, jadi secara teoritis Bat bisa menimpa alih-alih menyembunyikan metode tersebut. Akhirnya,bat2
adalahBak
, 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,
Bak
tetapi resolusi pada setiap tingkat tipe variabel dilakukan dengan mulai dari tingkat rantai pewarisan dan menelusuri hingga menimpa eksplisit metode yang paling diturunkan , yaitu pada merekaBar
,Bai
danBat
. 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.sumber
Silakan baca tentang polimorfisme dalam C #: Polimorfisme (Panduan Pemrograman C #)
Ini adalah contoh dari sana:
sumber
Anda harus membuatnya
virtual
lalu menimpa fungsi itu diTeacher
. Saat Anda mewarisi dan menggunakan pointer basis untuk merujuk ke kelas turunan, Anda perlu menimpanya menggunakanvirtual
.new
adalah untuk menyembunyikanbase
metode kelas pada referensi kelas turunan dan bukanbase
referensi kelas.sumber
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.
Anomali kecil lain adalah untuk baris kode berikut:
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!
sumber
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.
sumber
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 ()
sumber
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.
sumber
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.
sumber
Compiler melakukan ini karena tidak tahu itu adalah a
Teacher
. Yang ia tahu adalah bahwa itu adalahPerson
atau sesuatu yang berasal darinya. Jadi yang bisa dilakukan hanyalah memanggilPerson.ShowInfo()
metode.sumber
Hanya ingin memberikan jawaban singkat -
Anda harus menggunakan
virtual
danoverride
di kelas yang bisa diganti. Gunakanvirtual
untuk metode yang dapat ditimpa oleh kelas anak dan gunakanoverride
untuk metode yang harus menggantivirtual
metode tersebut.sumber
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.
sumber
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 {
}
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.
sumber
Contoh LinQPad untuk memulai secara membabi buta dan mengurangi duplikasi kode yang saya pikir adalah apa yang Anda coba lakukan.
sumber