Menu

Inheritance C#: base, virtual, override, sealed dan Polimorfisme

Cara kerja pewarisan class di C#: membuat turunan dengan titik dua, apa yang diwariskan dan apa yang tidak, memanggil base class dengan base, virtual dan override, menyembunyikan dengan new (dan output yang mengejutkan semua orang), sealed, polimorfisme, serta casting naik dan turun dalam hierarki.

Halaman ini dilengkapi editor yang bisa dijalankan. Edit, jalankan, dan lihat output-nya seketika.

Inheritance (pewarisan) membuat sebuah class bisa dibangun di atas class lain. Class turunan mendapat member base class, menambahkan member sendiri, dan boleh mengganti perilaku yang diizinkan base class untuk diganti. Digabung dengan method virtual, inheritance memberimu polimorfisme: kode yang ditulis untuk base class menjalankan perilaku turunan yang tepat tanpa perlu tahu class turunan mana yang dipegangnya.

Membuat class turunan

Taruh base class setelah titik dua. Class turunan punya semua yang dimiliki base class, ditambah apa yang dideklarasikannya sendiri:

Output:

TX-19: 20 km, 2 fares
True

Apa yang didapat class turunan dan apa yang tidak:

  • Diwariskan: field, property, method, event dan tipe bersarang. Semuanya benar-benar ada di dalam objek.
  • Bisa diakses: hanya yang diizinkan base class: member public, protected dan internal. Member private milik Vehicle ada di dalam setiap Taxi, tapi kode Taxi tidak bisa menyebut namanya. Itulah sebabnya Km punya private set: Taxi bisa membacanya, dan hanya mengubahnya lewat Drive.
  • Tidak diwariskan: constructor. Taxi harus mendeklarasikan constructor-nya sendiri dan menyambung ke salah satu constructor Vehicle dengan : base(plate). Halaman constructor menunjukkan urutan keduanya dijalankan.

Setiap class pada akhirnya adalah turunan dari object, itulah sebabnya setiap objek punya ToString(), Equals() dan GetHashCode().

virtual dan override

Base class menandai sebuah method dengan virtual untuk menyatakan "class turunan boleh menyediakan versinya sendiri". Class turunan menggantinya dengan override. Di dalam override, base.Method() memanggil versi milik base class.

Output:

[email] to lea@example.com: Your order 1042 has shipped today
  (unsubscribe link appended)
[sms] to +351 912 000 111: Your order 1042 has...
[generic] to ops-team: Your order 1042 has shipped today

Variabel loop-nya bertipe Notification, tapi setiap objek tetap menampilkan dirinya dengan caranya sendiri. Itulah polimorfisme: pemanggilan n.Render(...) ditentukan saat program berjalan berdasarkan tipe sebenarnya dari objek itu. Perhatikan juga bahwa base.Render di Email memakai Channel, yang juga virtual, jadi method base mencetak email, bukan generic. Pemanggilan virtual di dalam base class tetap sampai ke override-nya.

Property juga bisa virtual, seperti yang ditunjukkan Channel. Field tidak bisa.

Compiler mewajibkan kedua kata kunci itu. Menulis override pada method yang bukan virtual adalah error CS0506 ("cannot override inherited member ... because it is not marked virtual, abstract, or override"). Tidak menulis override saat method base-nya virtual hanya menghasilkan warning, tapi artinya berubah total, seperti yang ditunjukkan bagian berikutnya.

new vs override: menyembunyikan, bukan meng-override

Kalau class turunan mendeklarasikan method dengan signature yang sama seperti method base tanpa menulis override, method itu menyembunyikan method base. Compiler memberi warning (CS0114 untuk method base yang virtual, CS0108 untuk lainnya) dan menyarankan kata kunci new, yang menghilangkan warning tapi tetap mempertahankan perilaku menyembunyikan:

Output:

Sales report
Report
Draft report

b dan c adalah jenis objek yang sama, tapi mencetak judul yang berbeda. Dengan new, method yang dipilih bergantung pada tipe variabelnya, yang ditentukan saat compile. Kode yang menangani laporan sebagai Report (sebuah list, parameter method, callback framework) tidak pernah melihat DraftReport.Title. Itu hampir tidak pernah yang kamu inginkan. Pakai override untuk polimorfisme; new terutama ada untuk kasus ketika base class yang tidak kamu kendalikan menambahkan member yang namanya bentrok dengan member yang sudah kamu punya.

sealed

sealed pada sebuah class melarang pembuatan turunan darinya:

sealed class Invoice { }
class CorrectedInvoice : Invoice { }
// error CS0509: 'CorrectedInvoice': cannot derive from sealed type 'Invoice'

string bersifat sealed, begitu juga banyak tipe di framework. Pada override, sealed menghentikan rantainya di tingkat itu:

class Shape { public virtual string Name() => "shape"; }
class Square : Shape { public sealed override string Name() => "square"; }
class Tile : Square { public override string Name() => "tile"; }
// error CS0239: 'Tile.Name()': cannot override inherited member 'Square.Name()' because it is sealed

Merancang class untuk diwariskan butuh usaha: menentukan apa yang virtual, apa yang boleh diandalkan class turunan, dan dalam urutan apa semuanya terjadi. Class yang tidak dirancang seperti itu lebih aman dibuat sealed, dan sealed bisa dibatalkan nanti tanpa merusak kode siapa pun, sedangkan membuka kembali class yang sudah diturunkan orang lain tidak bisa ditarik lagi. Pemanggilan member class sealed juga bisa sedikit lebih cepat, karena runtime tahu tidak ada override.

Satu base class, banyak interface

Class C# punya tepat satu base class. class Admin : User, Employee adalah error CS1721 ("cannot have multiple base classes"). Namun, sebuah class bisa mengimplementasikan interface sebanyak apa pun di samping base class-nya:

class Admin : User, IAuditable, IComparable<Admin>
{
    // base class first, then interfaces, in any order
}

Pakai base class untuk hubungan "adalah sejenis, dan berbagi implementasi dengan", dan interface untuk "bisa melakukan". Saat base class hanya ada untuk memaksa class turunan mengisi beberapa method, abstract class adalah alat yang tepat.

Casting naik dan turun dalam hierarki

Objek turunan selalu bisa dipakai di tempat yang mengharapkan tipe base-nya. Upcast seperti itu terjadi secara implisit dan tidak mungkin gagal. Arah sebaliknya, downcast, butuh cast eksplisit dan gagal saat program berjalan kalau objeknya bukan tipe tersebut:

Output:

Rex fetches the ball
InvalidCastException: Tom is not a Dog
True
Rex fetches the ball

Menulis Dog d = pet; tanpa cast adalah error saat compile (CS0266: "An explicit conversion exists (are you missing a cast?)"), karena compiler hanya tahu bahwa pet adalah suatu Animal. Utamakan is dengan variabel saat objeknya mungkin bertipe lain, dan pakai cast biasa hanya saat kemungkinan lain berarti sebuah bug. Downcasting yang sering adalah tanda desain yang kurang baik: biasanya itu berarti perilakunya seharusnya ada di method virtual pada base class. Halaman pattern matching membahas bentuk-bentuk is secara lengkap.

Inheritance dan koleksi

Polimorfisme bekerja per elemen, tapi koleksi generik dari tipe turunan bukan koleksi dari tipe base: List<Animal> animals = new List<Dog>(); tidak bisa di-compile, karena list itu nantinya akan menerima Cat. Tampilan read-only bersifat kovarian, jadi IEnumerable<Animal> animals = new List<Dog>(); tidak masalah. Halaman generics menjelaskan alasannya.

Kesalahan yang sering terjadi

  • Lupa menulis override. Method-nya tetap bisa di-compile dengan warning dan diam-diam menyembunyikan, bukan meng-override. Perlakukan CS0114 sebagai error.
  • Membuat semuanya virtual. Setiap member virtual adalah janji kepada class turunan tentang kapan member itu dipanggil dan apa yang boleh diasumsikannya. Tandai hanya titik perluasan yang memang kamu maksudkan.
  • Hierarki yang terlalu dalam. Tiga atau empat tingkat inheritance membuat sulit mengetahui versi method mana yang dijalankan. Komposisi, yaitu class yang memegang objek lain lalu memanggilnya, sering kali lebih sederhana daripada menambah satu tingkat lagi.
  • Memanggil method virtual dari constructor. Override-nya berjalan sebelum badan constructor turunan, jadi field apa pun yang diberi nilai oleh badan itu masih berisi nilai default-nya.
  • Memakai inheritance hanya untuk memakai ulang kode. Kalau Stack mewarisi List, pemanggil bisa melakukan Insert ke tengah stack-mu. Simpan sebuah List di field private sebagai gantinya.

Pertanyaan yang sering diajukan

Bagaimana cara kerja inheritance di C#?

Sebuah class menyebut satu base class setelah titik dua: class Dog : Animal. Class turunan mendapat semua member base class (field, property, method, event), bisa menambahkan member sendiri, dan bisa meng-override member yang ditandai virtual oleh base class. Constructor tidak diwariskan, dan member private, walaupun ada di dalam objek, tidak bisa diakses dari class turunan.

Apa perbedaan virtual dan override di C#?

virtual ditaruh pada method di base class dan menyatakan "class turunan boleh mengganti ini". override ditaruh pada method di class turunan dan melakukan penggantiannya. Keduanya dibutuhkan: meng-override method yang bukan virtual, abstract atau sudah override adalah error CS0506. Saat method virtual dipanggil, runtime menjalankan versi untuk tipe objek yang sebenarnya, bukan tipe variabelnya.

Apa perbedaan new dan override di C#?

override mengganti method base untuk setiap pemanggil, bahkan kode yang memegang objek itu lewat variabel bertipe base class. new hanya menyembunyikannya: kode yang melihat objek sebagai tipe turunan memanggil method baru, sedangkan kode yang melihatnya sebagai tipe base tetap memanggil method base. Jadi Base b = new Derived(); b.M(); menjalankan Derived.M dengan override dan Base.M dengan new.

Apakah C# mendukung multiple inheritance?

Tidak untuk class: sebuah class punya tepat satu base class, dan menuliskan dua adalah error CS1721. Sebuah class bisa mengimplementasikan interface sebanyak apa pun, dan dengan cara inilah C# memodelkan "tipe ini bisa melakukan beberapa hal". Sejak C# 8, interface juga bisa membawa implementasi default untuk method.

Apa arti sealed di C#?

Class sealed tidak bisa dipakai sebagai base class; membuat turunan darinya adalah error CS0509. Contohnya, string bersifat sealed. Pada method, sealed override mencegah class yang lebih bawah dalam hierarki untuk meng-override-nya lagi (CS0239). Membuat sealed class yang tidak dirancang untuk diwariskan adalah pilihan default yang masuk akal.

Bagaimana cara memanggil method base class di C#?

Pakai base.MethodName(...) di dalam class turunan, biasanya di dalam override: public override string Describe() => base.Describe() + " with GPS";. Untuk constructor, pakai : base(...) setelah daftar parameter untuk memilih constructor base mana yang dijalankan.

Ilustrasi bahasa pemrograman di Coddy

Belajar coding dengan Coddy

Mulai