Bagikan melalui


Pewarisan dalam C# dan .NET

Tutorial ini memperkenalkan Anda mengenai pewarisan dalam C#. Pewarisan adalah fitur bahasa pemrograman berorientasi objek yang memungkinkan Anda menentukan kelas dasar yang menyediakan fungsionalitas tertentu (data dan perilaku) dan untuk menentukan kelas turunan yang mewarisi atau menimpa fungsionalitas tersebut.

Prasyarat

Instruksi penginstalan

Di Windows, file konfigurasi WinGet ini untuk menginstal semua prasyarat. Jika Anda sudah memiliki sesuatu yang terinstal, WinGet akan melewati langkah tersebut.

  1. Unduh file dan klik dua kali untuk menjalankannya.
  2. Baca perjanjian lisensi, ketik y, dan pilih Masukkan saat diminta untuk menerima.
  3. Jika Anda mendapatkan perintah Kontrol Akun Pengguna (UAC) yang berkedip di Taskbar Anda, izinkan penginstalan dilanjutkan.

Pada platform lain, Anda perlu menginstal masing-masing komponen ini secara terpisah.

  1. Unduh penginstal yang direkomendasikan dari halaman unduhan .NET SDK dan klik dua kali untuk menjalankannya. Halaman unduhan mendeteksi platform Anda dan merekomendasikan penginstal terbaru untuk platform Anda.
  2. Unduh penginstal terbaru dari halaman beranda Visual Studio Code dan klik dua kali untuk menjalankannya. Halaman itu juga mendeteksi platform Anda dan tautan harus benar untuk sistem Anda.
  3. Klik tombol "Instal" pada halaman ekstensi C# DevKit. Yang membuka kode Visual Studio, dan menanyakan apakah Anda ingin menginstal atau mengaktifkan ekstensi. Pilih "instal".

Menjalankan pengujian

Untuk membuat dan menjalankan contoh dalam tutorial ini, Anda menggunakan utilitas dotnet dari baris perintah. Ikuti langkah-langkah ini untuk setiap contoh:

  1. Buat direktori untuk menyimpan contoh.

  2. Masukkan perintah dotnet new console di prompt perintah untuk membuat proyek .NET Core baru.

  3. Salin dan tempel kode dari contoh ke editor kode Anda.

  4. Masukkan perintah dotnet restore dari baris perintah untuk memuat atau memulihkan dependensi proyek.

    Anda tidak harus menjalankan dotnet restore karena dijalankan secara implisit oleh semua perintah yang memerlukan terjadinya pemulihan, seperti dotnet new, dotnet build, dotnet run, dotnet test, dotnet publish, dan dotnet pack. Untuk menonaktifkan pemulihan implisit, gunakan opsi --no-restore.

    Perintah dotnet restore masih berguna dalam skenario tertentu di mana pemulihan secara eksplisit masuk akal, seperti pembangunan integrasi berkelanjutan di Azure DevOps Services atau dalam sistem pembangunan yang perlu secara eksplisit mengontrol saat pemulihan terjadi.

    Untuk informasi tentang cara mengelola umpan NuGet, lihat dotnet restore dokumentasi.

  5. Masukkan perintah dotnet run untuk mengompilasi dan menjalankan contoh.

Latar belakang: Apa itu warisan?

Inheritance adalah salah satu atribut dasar pemrograman berorientasi objek. Ini memungkinkan Anda untuk menentukan kelas anak yang menggunakan kembali (mewarisi), memperluas, atau memodifikasi perilaku kelas induk. Kelas yang anggotanya diwariskan disebut kelas dasar . Kelas yang mewarisi anggota kelas dasar disebut kelas turunan .

C# dan .NET hanya mendukung pewarisan tunggal. Artinya, sebuah kelas hanya dapat mewarisi dari satu kelas. Namun, pewarisan bersifat transitif, yang memungkinkan Anda menentukan hierarki warisan untuk sekumpulan jenis. Dengan kata lain, tipe D dapat mewarisi tipe C, yang mewarisi tipe B, yang mewarisi tipe kelas dasar A. Karena pewarisan bersifat transitif, anggota tipe A tersedia untuk tipe D.

Tidak semua anggota kelas dasar diwariskan oleh kelas turunan. Anggota berikut ini tidak diwariskan:

  • Konstruktor statis, yang menginisialisasi data statis kelas.

  • Konstruktor Instans, yang Anda panggil untuk membuat instans baru dari kelas. Setiap kelas harus mendefinisikan konstruktornya sendiri.

  • Finalizers, yang dipanggil oleh pengumpul sampah pada runtime untuk menghancurkan instans dari kelas.

Sementara semua anggota lain dari kelas dasar diwariskan oleh kelas turunan, apakah mereka terlihat atau tidak tergantung pada aksesibilitas mereka. Aksesibilitas anggota memengaruhi visibilitasnya untuk kelas turunan sebagai berikut:

  • Anggota Private hanya terlihat pada kelas turunan yang terbenam di dalam kelas dasar mereka. Jika tidak, mereka tidak terlihat di kelas turunan. Dalam contoh berikut, A.B adalah kelas berlapis yang berasal dari A, dan C berasal dari A. Bidang A._value privat terlihat di A.B. Namun, jika Anda menghapus komentar dari metode C.GetValue dan mencoba mengkompilasi contoh, itu menghasilkan kesalahan kompilator CS0122: "'A._value' tidak dapat diakses karena tingkat perlindungannya."

    public class A
    {
        private int _value = 10;
    
        public class B : A
        {
            public int GetValue()
            {
                return _value;
            }
        }
    }
    
    public class C : A
    {
        //    public int GetValue()
        //    {
        //        return _value;
        //    }
    }
    
    public class AccessExample
    {
        public static void Main(string[] args)
        {
            var b = new A.B();
            Console.WriteLine(b.GetValue());
        }
    }
    // The example displays the following output:
    //       10
    
  • Anggota terlindungi hanya terlihat di kelas turunan.

  • Anggota internal hanya terlihat di kelas turunan yang terletak dalam assembly yang sama dengan kelas dasar. Mereka tidak terlihat di kelas turunan yang terletak di rakitan yang berbeda dari kelas dasar.

  • Anggota publik dapat diakses di kelas turunan dan menjadi bagian dari antarmuka publik kelas turunan. Anggota yang diwariskan publik dapat dipanggil seolah-olah mereka didefinisikan dalam kelas turunan. Dalam contoh berikut, A kelas menentukan metode bernama Method1, dan B kelas mewarisi dari kelas A. Contoh kemudian memanggil Method1 seolah-olah itu adalah metode instans pada B.

    public class A
    {
        public void Method1()
        {
            // Method implementation.
        }
    }
    
    public class B : A
    { }
    
    public class Example
    {
        public static void Main()
        {
            B b = new ();
            b.Method1();
        }
    }
    

Kelas turunan juga dapat mengambil alih anggota yang diwariskan dengan memberikan implementasi alternatif. Agar dapat meng-override anggota, anggota di kelas dasar harus ditandai dengan kata kunci virtual . Secara bawaan, anggota kelas dasar tidak ditandai sebagai virtual dan tidak dapat di-override. Mencoba menggantikan anggota non-virtual, seperti contoh berikut ini, mengakibatkan kesalahan kompilator CS0506: "<anggota> tidak dapat menggantikan anggota yang diwariskan <anggota> karena tidak ditandai sebagai virtual, abstrak, atau override."

public class A
{
    public void Method1()
    {
        // Do something.
    }
}

public class B : A
{
    public override void Method1() // Generates CS0506.
    {
        // Do something else.
    }
}

Dalam beberapa kasus, kelas turunan harus mengambil alih implementasi kelas dasar. Anggota kelas dasar yang ditandai dengan kata kunci abstrak mengharuskan kelas turunan mengambil alihnya. Mencoba mengkompilasi contoh berikut menghasilkan kesalahan kompilator CS0534, "<kelas> tidak menerapkan anggota abstrak yang diwariskan <anggota>", karena B kelas tidak menyediakan implementasi untuk A.Method1.

public abstract class A
{
    public abstract void Method1();
}

public class B : A // Generates CS0534.
{
    public void Method3()
    {
        // Do something.
    }
}

Pewarisan hanya berlaku untuk kelas dan antarmuka. Kategori jenis lain (struktur, delegasi, dan enum) tidak mendukung pewarisan. Karena aturan ini, mencoba mengompilasi kode seperti contoh berikut menghasilkan kesalahan kompilator CS0527: "Tipe 'ValueType' dalam daftar antarmuka bukanlah sebuah antarmuka." Pesan kesalahan menunjukkan bahwa, meskipun Anda dapat menentukan antarmuka yang diterapkan oleh struct, pewarisan tidak didukung.

public struct ValueStructure : ValueType // Generates CS0527.
{
}

Pewarisan implisit

Selain jenis apa pun yang dapat mereka warisi dari melalui warisan tunggal, semua jenis dalam sistem jenis .NET secara implisit mewarisi dari Object atau jenis yang berasal darinya. Fungsionalitas umum Object tersedia untuk semua jenis.

Untuk melihat arti pewarisan implisit, mari kita tentukan kelas baru, SimpleClass, itu hanyalah definisi kelas kosong:

public class SimpleClass
{ }

Anda kemudian dapat menggunakan pantulan (yang memungkinkan Anda memeriksa metadata jenis untuk mendapatkan informasi tentang jenis tersebut) untuk mendapatkan daftar anggota yang termasuk dalam jenis SimpleClass. Meskipun Anda belum menentukan anggota apa pun di kelas SimpleClass Anda, output dari contoh menunjukkan bahwa itu benar-benar memiliki sembilan anggota. Salah satu anggota ini adalah konstruktor tanpa parameter (atau default) yang secara otomatis disediakan untuk jenis SimpleClass oleh pengkompilasi C#. Delapan yang tersisa adalah anggota Object, jenis yang menjadi dasar semua kelas dan antarmuka dalam sistem jenis .NET dan pada akhirnya terwarisi secara implisit.

using System.Reflection;

public class SimpleClassExample
{
    public static void Main()
    {
        Type t = typeof(SimpleClass);
        BindingFlags flags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public |
                             BindingFlags.NonPublic | BindingFlags.FlattenHierarchy;
        MemberInfo[] members = t.GetMembers(flags);
        Console.WriteLine($"Type {t.Name} has {members.Length} members: ");
        foreach (MemberInfo member in members)
        {
            string access = "";
            string stat = "";
            var method = member as MethodBase;
            if (method != null)
            {
                if (method.IsPublic)
                    access = " Public";
                else if (method.IsPrivate)
                    access = " Private";
                else if (method.IsFamily)
                    access = " Protected";
                else if (method.IsAssembly)
                    access = " Internal";
                else if (method.IsFamilyOrAssembly)
                    access = " Protected Internal ";
                if (method.IsStatic)
                    stat = " Static";
            }
            string output = $"{member.Name} ({member.MemberType}): {access}{stat}, Declared by {member.DeclaringType}";
            Console.WriteLine(output);
        }
    }
}
// The example displays the following output:
//	Type SimpleClass has 9 members:
//	ToString (Method):  Public, Declared by System.Object
//	Equals (Method):  Public, Declared by System.Object
//	Equals (Method):  Public Static, Declared by System.Object
//	ReferenceEquals (Method):  Public Static, Declared by System.Object
//	GetHashCode (Method):  Public, Declared by System.Object
//	GetType (Method):  Public, Declared by System.Object
//	Finalize (Method):  Internal, Declared by System.Object
//	MemberwiseClone (Method):  Internal, Declared by System.Object
//	.ctor (Constructor):  Public, Declared by SimpleClass

Pewarisan implisit dari kelas Object membuat metode ini tersedia untuk kelas SimpleClass:

  • Metode ToString publik, yang mengonversi objek SimpleClass ke representasi stringnya, mengembalikan nama jenis yang sepenuhnya memenuhi syarat. Dalam hal ini, metode ToString mengembalikan string "SimpleClass".

  • Tiga metode yang menguji kesetaraan dua objek: metode instans publik Equals(Object), metode statis publik Equals(Object, Object), dan metode statis publik ReferenceEquals(Object, Object). Secara bawaan, metode-metode ini menguji kesetaraan referensi objek; artinya, agar dianggap sama, dua variabel objek harus merujuk ke objek yang sama.

  • Metode publik GetHashCode, yang menghitung nilai yang memungkinkan sebuah instans dari tipe digunakan dalam koleksi ter-hash.

  • Metode GetType publik, yang mengembalikan objek Type yang mewakili jenis SimpleClass.

  • Metode Finalize yang dilindungi ini dirancang untuk melepaskan sumber daya non-memori yang dikelola manual sebelum memori objek dikembalikan oleh pengumpul sampah.

  • Metode MemberwiseClone yang dilindungi, yang menciptakan kloning dangkal dari objek saat ini.

Karena pewarisan implisit, Anda dapat memanggil anggota yang diwariskan dari objek SimpleClass seolah-olah itu benar-benar anggota yang ditentukan dalam kelas SimpleClass. Misalnya, contoh berikut memanggil metode SimpleClass.ToString, yang SimpleClass mewarisi dari Object.

public class EmptyClass
{ }

public class ClassNameExample
{
    public static void Main()
    {
        EmptyClass sc = new();
        Console.WriteLine(sc.ToString());
    }
}
// The example displays the following output:
//        EmptyClass

Tabel berikut ini mencantumkan kategori jenis yang bisa Anda buat di C# dan jenis dari mana mereka secara implisit mewarisi. Setiap tipe dasar menyediakan seperangkat anggota yang berbeda melalui pewarisan untuk tipe yang diturunkan secara implisit.

Jenis kategori Secara implisit mewarisi dari
kelas Object
struktur ValueType, Object
enum Enum, , ValueTypeObject
Mendelegasikan MulticastDelegate, , DelegateObject

Pewarisan dan hubungan 'is a'

Biasanya, inheritansi digunakan untuk menyatakan hubungan "adalah" antara kelas dasar dan satu atau beberapa kelas turunan, di mana kelas turunan merupakan versi khusus dari kelas dasar; kelas turunan adalah tipe dari kelas dasar. Misalnya, kelas Publication mewakili publikasi dalam bentuk apa pun, dan kelas Book dan Magazine mewakili jenis publikasi tertentu.

Nota

Kelas atau struktur dapat mengimplementasikan satu atau beberapa antarmuka. Meskipun implementasi antarmuka sering disajikan sebagai solusi untuk warisan tunggal atau sebagai cara menggunakan pewarisan dengan struktur, ini dimaksudkan untuk mengekspresikan hubungan yang berbeda (hubungan "dapat melakukan") antara antarmuka dan jenis penerapannya daripada pewarisan. Antarmuka mendefinisikan subset fungsionalitas (seperti kemampuan untuk menguji kesetaraan, membandingkan atau mengurutkan objek, atau untuk mendukung penguraian dan pemformatan sensitif budaya) yang disediakan antarmuka untuk jenis penerapannya.

Perhatikan bahwa "adalah" juga mengekspresikan hubungan antara suatu jenis dan instansiasi spesifik dari jenis tersebut. Dalam contoh berikut, Automobile adalah kelas yang memiliki tiga properti baca-saja unik: Make, produsen mobil; Model, jenis mobil; dan Year, tahun pembuatannya. Kelas Automobile Anda juga memiliki konstruktor yang argumennya ditetapkan ke nilai properti, dan mengambil alih metode Object.ToString untuk menghasilkan string yang secara unik mengidentifikasi instans Automobile daripada kelas Automobile.

public class Automobile
{
    public Automobile(string make, string model, int year)
    {
        if (make == null)
            throw new ArgumentNullException(nameof(make), "The make cannot be null.");
        else if (string.IsNullOrWhiteSpace(make))
            throw new ArgumentException("make cannot be an empty string or have space characters only.");
        Make = make;

        if (model == null)
            throw new ArgumentNullException(nameof(model), "The model cannot be null.");
        else if (string.IsNullOrWhiteSpace(model))
            throw new ArgumentException("model cannot be an empty string or have space characters only.");
        Model = model;

        if (year < 1857 || year > DateTime.Now.Year + 2)
            throw new ArgumentException("The year is out of range.");
        Year = year;
    }

    public string Make { get; }

    public string Model { get; }

    public int Year { get; }

    public override string ToString() => $"{Year} {Make} {Model}";
}

Dalam hal ini, Anda tidak boleh mengandalkan warisan untuk mewakili pembuatan dan model mobil tertentu. Misalnya, Anda tidak perlu menentukan jenis Packard untuk mewakili mobil yang diproduksi oleh Packard Motor Car Company. Sebagai gantinya, Anda dapat mewakilinya dengan membuat objek Automobile dengan nilai yang sesuai yang diteruskan ke konstruktor kelasnya, seperti contoh berikut.

using System;

public class Example
{
    public static void Main()
    {
        var packard = new Automobile("Packard", "Custom Eight", 1948);
        Console.WriteLine(packard);
    }
}
// The example displays the following output:
//        1948 Packard Custom Eight

Hubungan is-a berdasarkan warisan paling baik diterapkan ke kelas dasar dan ke kelas turunan yang menambahkan anggota tambahan ke kelas dasar atau yang memerlukan fungsionalitas tambahan yang tidak ada di kelas dasar.

Merancang kelas dasar dan kelas turunan

Mari kita lihat proses merancang kelas dasar dan kelas turunannya. Di bagian ini, Anda akan menentukan kelas dasar, Publication, yang mewakili publikasi dalam bentuk apa pun, seperti buku, majalah, koran, jurnal, artikel, dll. Anda juga akan menentukan kelas Book yang berasal dari Publication. Anda dapat dengan mudah memperluas contoh untuk menentukan kelas turunan lainnya, seperti Magazine, Journal, Newspaper, dan Article.

Kelas Publikasi dasar

Dalam merancang kelas Publication, Anda perlu membuat beberapa keputusan desain:

  • Anggota apa yang akan disertakan dalam kelas Publication dasar Anda, dan apakah anggota Publication menyediakan implementasi metode atau apakah Publication adalah kelas dasar abstrak yang berfungsi sebagai templat untuk kelas turunannya.

    Dalam hal ini, kelas Publication akan menyediakan implementasi metode. Bagian Merancang kelas dasar abstrak dan kelas turunannya berisi contoh yang menggunakan kelas dasar abstrak untuk menentukan metode yang harus diambil alih kelas turunannya. Kelas turunan bebas untuk memberikan implementasi apa pun yang cocok untuk jenis turunan.

    Kemampuan untuk menggunakan kembali kode (yaitu, beberapa kelas turunan berbagi deklarasi dan implementasi metode kelas dasar dan tidak perlu mengambil alihnya) adalah keuntungan dari kelas dasar non-abstrak. Oleh karena itu, Anda harus menambahkan anggota ke Publication jika kode mereka kemungkinan akan digunakan oleh beberapa atau sebagian besar jenis Publication yang khusus. Jika Anda gagal memberikan implementasi kelas dasar secara efisien, Anda akhirnya harus memberikan implementasi anggota yang sebagian besar identik di kelas turunan, bukan satu implementasi di kelas dasar. Kebutuhan untuk mempertahankan kode duplikat di beberapa lokasi adalah sumber potensial bug.

    Baik untuk memaksimalkan penggunaan kembali kode dan untuk membuat hierarki warisan logis dan intuitif, Anda ingin memastikan bahwa Anda menyertakan dalam kelas Publication hanya data dan fungsionalitas yang umum untuk semua atau sebagian besar publikasi. Kelas turunan kemudian menerapkan anggota yang unik untuk jenis publikasi tertentu yang mereka wakili.

  • Seberapa jauh untuk memperluas hierarki kelas Anda. Apakah Anda ingin mengembangkan hierarki tiga kelas atau lebih, daripada hanya kelas dasar dan satu atau beberapa kelas turunan? Misalnya, Publication bisa menjadi kelas dasar Periodical, yang pada gilirannya adalah kelas dasar Magazine, Journal dan Newspaper.

    Misalnya, Anda akan menggunakan hierarki kecil kelas Publication dan satu kelas turunan, Book. Anda dapat dengan mudah memperluas contoh untuk membuat sejumlah kelas tambahan yang berasal dari Publication, seperti Magazine dan Article.

  • Apakah masuk akal untuk menginisialisasi kelas dasar. Jika tidak, Anda harus menerapkan kata kunci abstrak ke kelas. Jika tidak, kelas Publication Anda dapat dibuat dengan memanggil konstruktor kelas tersebut. Jika upaya dilakukan untuk membuat instans kelas yang ditandai dengan kata kunci abstract dengan panggilan langsung ke konstruktor kelasnya, kompilator C# menghasilkan kesalahan CS0144, "Tidak dapat membuat instans kelas atau antarmuka abstrak." Jika upaya dilakukan untuk membuat instans kelas dengan menggunakan pantulan, metode pantulan akan melemparkan MemberAccessException.

    Secara default, kelas dasar dapat dibuat dengan memanggil konstruktor kelasnya. Anda tidak perlu secara eksplisit menentukan konstruktor kelas. Jika tidak ada dalam kode sumber kelas dasar, pengkompilasi C# secara otomatis menyediakan konstruktor default (tanpa parameter).

    Misalnya, Anda akan menandai kelas Publication sebagai abstrak agar tidak dapat diinstansiasi. Kelas abstract tanpa metode abstract menunjukkan bahwa kelas ini mewakili konsep abstrak yang dibagikan di antara beberapa kelas konkret (seperti Book, Journal).

  • Apakah kelas turunan harus mewarisi implementasi kelas dasar anggota tertentu, apakah mereka memiliki opsi untuk mengambil alih implementasi kelas dasar, atau apakah mereka harus memberikan implementasi. Anda menggunakan kata kunci abstrak untuk memaksa kelas turunan untuk memberikan implementasi. Anda menggunakan kata kunci virtual untuk memungkinkan kelas turunan menggantikan metode kelas dasar. Secara default, metode yang didefinisikan dalam kelas dasar tidak dapat diganti.

    Kelas Publication tidak memiliki metode abstract, tetapi kelas itu sendiri abstract.

  • Apakah kelas turunan mewakili kelas akhir dalam hierarki warisan dan tidak dapat digunakan sebagai kelas dasar untuk kelas turunan tambahan. Secara default, kelas apa pun dapat berfungsi sebagai kelas dasar. Anda dapat menerapkan kata kunci dengan atribut 'sealed' untuk menunjukkan bahwa kelas tersebut tidak dapat digunakan sebagai kelas dasar untuk kelas lain. Mencoba untuk menurunkan dari kelas tersegel menghasilkan kesalahan kompilator CS0509, "tidak dapat menurunkan dari tipe tersegel <typeName>."

    Misalnya, Anda akan menandai kelas turunan Anda sebagai sealed.

Contoh berikut menunjukkan kode sumber untuk kelas Publication, serta enumerasi PublicationType yang dikembalikan oleh properti Publication.PublicationType. Selain anggota yang diwarisi dari Object, kelas Publication mendefinisikan anggota unik dan penggantian anggota yang berikut:


public enum PublicationType { Misc, Book, Magazine, Article };

public abstract class Publication
{
    private bool _published = false;
    private DateTime _datePublished;
    private int _totalPages;

    public Publication(string title, string publisher, PublicationType type)
    {
        if (string.IsNullOrWhiteSpace(publisher))
            throw new ArgumentException("The publisher is required.");
        Publisher = publisher;

        if (string.IsNullOrWhiteSpace(title))
            throw new ArgumentException("The title is required.");
        Title = title;

        Type = type;
    }

    public string Publisher { get; }

    public string Title { get; }

    public PublicationType Type { get; }

    public string? CopyrightName { get; private set; }

    public int CopyrightDate { get; private set; }

    public int Pages
    {
        get { return _totalPages; }
        set
        {
            if (value <= 0)
                throw new ArgumentOutOfRangeException(nameof(value), "The number of pages cannot be zero or negative.");
            _totalPages = value;
        }
    }

    public string GetPublicationDate()
    {
        if (!_published)
            return "NYP";
        else
            return _datePublished.ToString("d");
    }

    public void Publish(DateTime datePublished)
    {
        _published = true;
        _datePublished = datePublished;
    }

    public void Copyright(string copyrightName, int copyrightDate)
    {
        if (string.IsNullOrWhiteSpace(copyrightName))
            throw new ArgumentException("The name of the copyright holder is required.");
        CopyrightName = copyrightName;

        int currentYear = DateTime.Now.Year;
        if (copyrightDate < currentYear - 10 || copyrightDate > currentYear + 2)
            throw new ArgumentOutOfRangeException($"The copyright year must be between {currentYear - 10} and {currentYear + 1}");
        CopyrightDate = copyrightDate;
    }

    public override string ToString() => Title;
}
  • Konstruktor

    Karena kelas Publicationabstract, kelas tersebut tidak dapat diinstansiasi langsung dari kode seperti contoh berikut:

    var publication = new Publication("Tiddlywinks for Experts", "Fun and Games",
                                      PublicationType.Book);
    

    Namun, konstruktor instansnya dapat dipanggil langsung dari konstruktor kelas turunan, seperti yang ditunjukkan kode sumber untuk kelas Book.

  • Dua properti terkait publikasi

    Title adalah properti String baca-saja yang nilainya disediakan dengan memanggil konstruktor Publication.

    Pages adalah properti Int32 baca-tulis yang menunjukkan berapa banyak total halaman yang dimiliki publikasi. Nilai disimpan dalam bidang privat bernama totalPages. Ini harus berupa angka positif atau ArgumentOutOfRangeException dilemparkan.

  • Anggota terkait penerbit

    Dua atribut baca-saja, Publisher dan Type. Nilai-nilai tersebut awalnya disediakan dari panggilan ke konstruktor kelas Publication.

  • Anggota terkait penerbitan

    Dua metode, Publish dan GetPublicationDate, atur dan kembalikan tanggal publikasi. Metode Publish menetapkan penanda privat published ke true ketika dipanggil dan menetapkan tanggal yang diteruskan sebagai argumen ke bidang privat datePublished. Metode GetPublicationDate mengembalikan string "NYP" jika bendera published adalah false, dan nilai bidang datePublished jika itu true.

  • Anggota yang terkait dengan hak cipta

    Metode Copyright mengambil nama pemegang hak cipta dan tahun hak cipta sebagai argumen dan menetapkannya ke properti CopyrightName dan CopyrightDate.

  • Penggantian metode ToString

    Jika tipe tidak menggantikan metode Object.ToString, tipe tersebut akan mengembalikan nama lengkap tipe tersebut, yang kurang berguna dalam membedakan satu instance dari yang lain. Kelas Publication mengambil alih Object.ToString untuk mengembalikan nilai properti Title.

Gambar berikut mengilustrasikan hubungan antara kelas Publication dasar Anda dan kelas Object yang diwarisi secara implisit.

Kelas Objek dan Publikasi

Kelas Book

Kelas Book mewakili buku sebagai jenis publikasi khusus. Contoh berikut menunjukkan kode sumber untuk kelas Book.

using System;

public sealed class Book : Publication
{
    public Book(string title, string author, string publisher) :
           this(title, string.Empty, author, publisher)
    { }

    public Book(string title, string isbn, string author, string publisher) : base(title, publisher, PublicationType.Book)
    {
        // isbn argument must be a 10- or 13-character numeric string without "-" characters.
        // We could also determine whether the ISBN is valid by comparing its checksum digit
        // with a computed checksum.
        //
        if (!string.IsNullOrEmpty(isbn))
        {
            // Determine if ISBN length is correct.
            if (!(isbn.Length == 10 | isbn.Length == 13))
                throw new ArgumentException("The ISBN must be a 10- or 13-character numeric string.");
            if (!ulong.TryParse(isbn, out _))
                throw new ArgumentException("The ISBN can consist of numeric characters only.");
        }
        ISBN = isbn;

        Author = author;
    }

    public string ISBN { get; }

    public string Author { get; }

    public decimal Price { get; private set; }

    // A three-digit ISO currency symbol.
    public string? Currency { get; private set; }

    // Returns the old price, and sets a new price.
    public decimal SetPrice(decimal price, string currency)
    {
        if (price < 0)
            throw new ArgumentOutOfRangeException(nameof(price), "The price cannot be negative.");
        decimal oldValue = Price;
        Price = price;

        if (currency.Length != 3)
            throw new ArgumentException("The ISO currency symbol is a 3-character string.");
        Currency = currency;

        return oldValue;
    }

    public override bool Equals(object? obj)
    {
        if (obj is not Book book)
            return false;
        else
            return ISBN == book.ISBN;
    }

    public override int GetHashCode() => ISBN.GetHashCode();

    public override string ToString() => $"{(string.IsNullOrEmpty(Author) ? "" : Author + ", ")}{Title}";
}

Selain anggota yang diwarisi dari Publication, kelas Book mendefinisikan anggota unik dan penggantian anggota yang berikut:

  • Dua konstruktor

    Dua konstruktor Book berbagi tiga parameter umum. Dua, judul dan penerbit, sesuai dengan parameter konstruktor Publication. Yang ketiga adalah penulis, yang disimpan dalam properti Author publik yang tidak dapat diubah. Satu konstruktor mencakup parameter isbn, yang disimpan di properti otomatis ISBN.

    Konstruktor pertama menggunakan kata kunci ini untuk memanggil konstruktor lain. Penautan konstruktor adalah pola umum dalam menentukan konstruktor. Konstruktor dengan parameter yang lebih sedikit memberikan nilai default saat memanggil konstruktor dengan jumlah parameter terbesar.

    Konstruktor kedua menggunakan kata kunci dasar untuk meneruskan judul dan nama penerbit ke konstruktor kelas dasar. Jika Anda tidak melakukan panggilan eksplisit ke konstruktor kelas dasar dalam kode sumber Anda, kompilator C# secara otomatis memasok panggilan ke konstruktor default atau tanpa parameter kelas dasar.

  • Properti ISBN hanya-baca, yang mengembalikan Nomor Buku Standar Internasional objek Book, angka unik dengan 10 atau 13 digit. ISBN diberikan sebagai parameter ke salah satu konstruktor Book. ISBN disimpan di field pendukung privat, yang dihasilkan secara otomatis oleh pengompilasi.

  • Properti Author baca-saja. Nama penulis disediakan sebagai argumen untuk konstruktor Book dan disimpan dalam properti.

  • Dua properti terkait harga hanya-baca, Price dan Currency. Nilainya disediakan sebagai argumen dalam panggilan metode SetPrice. Properti Currency adalah simbol mata uang ISO tiga digit (misalnya, USD untuk dolar AS). Simbol mata uang ISO dapat diambil dari properti ISOCurrencySymbol. Kedua properti ini bersifat baca-saja secara eksternal, tetapi keduanya dapat diatur berdasarkan kode di kelas Book.

  • Metode SetPrice, yang mengatur nilai properti Price dan Currency. Nilai-nilai tersebut dikembalikan oleh properti-properti yang sama.

  • Menggantikan metode ToString (diwarisi dari Publication) serta metode Object.Equals(Object) dan GetHashCode (diwarisi dari Object).

    Kecuali diubah, metode Object.Equals(Object) menguji kesetaraan referensi. Artinya, dua variabel objek dianggap sama jika merujuk ke objek yang sama. Di kelas Book, di sisi lain, dua objek Book harus sama jika memiliki ISBN yang sama.

    Saat Anda mengambil alih metode Object.Equals(Object), Anda juga harus mengambil alih metode GetHashCode, yang mengembalikan nilai yang digunakan runtime untuk menyimpan item dalam koleksi yang di-hash untuk pengambilan yang efisien. Kode hash harus mengembalikan nilai yang konsisten dengan pengujian untuk kesetaraan. Karena Anda telah menimpa Object.Equals(Object) untuk mengembalikan true jika properti ISBN dari dua objek Book sama, Anda harus mengembalikan kode hash yang dihitung dengan memanggil metode GetHashCode dari string yang dikembalikan oleh properti ISBN.

Gambar berikut mengilustrasikan hubungan antara kelas Book dan Publication, kelas dasarnya.

kelas Publikasi dan Buku

Anda sekarang dapat membuat instans objek Book, memanggil anggotanya yang unik dan diwariskan, dan meneruskannya sebagai argumen ke metode yang mengharapkan parameter jenis Publication atau jenis Book, seperti yang ditunjukkan contoh berikut.

public class ClassExample
{
    public static void Main()
    {
        var book = new Book("The Tempest", "0971655819", "Shakespeare, William",
                            "Public Domain Press");
        ShowPublicationInfo(book);
        book.Publish(new DateTime(2016, 8, 18));
        ShowPublicationInfo(book);

        var book2 = new Book("The Tempest", "Classic Works Press", "Shakespeare, William");
        Console.Write($"{book.Title} and {book2.Title} are the same publication: " +
              $"{((Publication)book).Equals(book2)}");
    }

    public static void ShowPublicationInfo(Publication pub)
    {
        string pubDate = pub.GetPublicationDate();
        Console.WriteLine($"{pub.Title}, " +
                  $"{(pubDate == "NYP" ? "Not Yet Published" : "published on " + pubDate):d} by {pub.Publisher}");
    }
}
// The example displays the following output:
//        The Tempest, Not Yet Published by Public Domain Press
//        The Tempest, published on 8/18/2016 by Public Domain Press
//        The Tempest and The Tempest are the same publication: False

Merancang kelas dasar abstrak dan kelas turunannya

Dalam contoh sebelumnya, Anda menentukan kelas dasar yang menyediakan implementasi untuk sejumlah metode untuk memungkinkan kelas turunan berbagi kode. Namun, dalam banyak kasus, kelas dasar tidak diharapkan untuk memberikan implementasi. Sebaliknya, kelas dasar adalah kelas abstrak yang mendeklarasikan metode abstrak ; ini berfungsi sebagai templat yang mendefinisikan anggota yang harus diterapkan oleh setiap kelas turunan. Biasanya dalam kelas dasar abstrak, implementasi setiap jenis turunan unik untuk jenis tersebut. Anda menandai kelas dengan kata kunci abstrak karena tidak masuk akal untuk membuat instans objek Publication, meskipun kelas memang menyediakan implementasi fungsionalitas yang umum untuk publikasi.

Misalnya, setiap bentuk geometrik dua dimensi tertutup mencakup dua properti: area, tingkat dalam bentuk; dan perimeter, atau jarak di sepanjang tepi bentuk. Namun, cara penghitungan properti ini sepenuhnya bergantung pada bentuk tertentu. Rumus untuk menghitung perimeter (atau lingkar) lingkaran, misalnya, berbeda dari persegi. Kelas Shape adalah kelas abstract dengan metode abstract. Itu menunjukkan kelas turunan memiliki fungsionalitas yang sama, tetapi kelas turunan tersebut mengimplementasikan fungsionalitas tersebut secara berbeda.

Contoh berikut mendefinisikan kelas dasar abstrak bernama Shape yang menentukan dua properti: Area dan Perimeter. Selain menandai kelas dengan kata kunci abstrak , setiap anggota instans juga ditandai dengan kata kunci abstrak . Dalam hal ini, Shape juga mengganti metode Object.ToString untuk mengembalikan nama jenis, bukan nama lengkap yang memenuhi syarat. Dan mendefinisikan dua anggota statis, GetArea dan GetPerimeter, yang memungkinkan pemanggil untuk dengan mudah mengambil area dan perimeter instance kelas turunan apa pun. Ketika Anda meneruskan sebuah instance dari kelas turunan ke salah satu metode ini, runtime akan memanggil override metode dari kelas turunan tersebut.

public abstract class Shape
{
    public abstract double Area { get; }

    public abstract double Perimeter { get; }

    public override string ToString() => GetType().Name;

    public static double GetArea(Shape shape) => shape.Area;

    public static double GetPerimeter(Shape shape) => shape.Perimeter;
}

Anda kemudian dapat memperoleh beberapa kelas dari Shape yang mewakili bentuk tertentu. Contoh berikut mendefinisikan tiga kelas, Square, Rectangle, dan Circle. Masing-masing menggunakan rumus yang unik untuk bentuk tertentu tersebut untuk menghitung area dan perimeter. Beberapa kelas turunan juga menentukan properti, seperti Rectangle.Diagonal dan Circle.Diameter, yang unik untuk bentuk yang diwakilinya.

using System;

public class Square : Shape
{
    public Square(double length)
    {
        Side = length;
    }

    public double Side { get; }

    public override double Area => Math.Pow(Side, 2);

    public override double Perimeter => Side * 4;

    public double Diagonal => Math.Round(Math.Sqrt(2) * Side, 2);
}

public class Rectangle : Shape
{
    public Rectangle(double length, double width)
    {
        Length = length;
        Width = width;
    }

    public double Length { get; }

    public double Width { get; }

    public override double Area => Length * Width;

    public override double Perimeter => 2 * Length + 2 * Width;

    public bool IsSquare() => Length == Width;

    public double Diagonal => Math.Round(Math.Sqrt(Math.Pow(Length, 2) + Math.Pow(Width, 2)), 2);
}

public class Circle : Shape
{
    public Circle(double radius)
    {
        Radius = radius;
    }

    public override double Area => Math.Round(Math.PI * Math.Pow(Radius, 2), 2);

    public override double Perimeter => Math.Round(Math.PI * 2 * Radius, 2);

    // Define a circumference, since it's the more familiar term.
    public double Circumference => Perimeter;

    public double Radius { get; }

    public double Diameter => Radius * 2;
}

Contoh berikut menggunakan objek yang berasal dari Shape. Ini membuat instans array dari objek yang berasal dari Shape dan memanggil metode statis dari kelas Shape, yang membungkus nilai properti Shape yang dikembalikan. Lingkungan eksekusi mengambil nilai dari properti yang ditimpa dari tipe turunan. Contoh ini juga mengubah setiap objek Shape dalam array ke jenis turunannya dan, jika pengubahan berhasil, mengambil properti dari subkelas tersebut dari Shape.

using System;

public class Example
{
    public static void Main()
    {
        Shape[] shapes = { new Rectangle(10, 12), new Square(5),
                    new Circle(3) };
        foreach (Shape shape in shapes)
        {
            Console.WriteLine($"{shape}: area, {Shape.GetArea(shape)}; " +
                              $"perimeter, {Shape.GetPerimeter(shape)}");
            if (shape is Rectangle rect)
            {
                Console.WriteLine($"   Is Square: {rect.IsSquare()}, Diagonal: {rect.Diagonal}");
                continue;
            }
            if (shape is Square sq)
            {
                Console.WriteLine($"   Diagonal: {sq.Diagonal}");
                continue;
            }
        }
    }
}
// The example displays the following output:
//         Rectangle: area, 120; perimeter, 44
//            Is Square: False, Diagonal: 15.62
//         Square: area, 25; perimeter, 20
//            Diagonal: 7.07
//         Circle: area, 28.27; perimeter, 18.85