Catatan
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba masuk atau mengubah direktori.
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba mengubah direktori.
C# 12 memperkenalkan konstruktor utama, yang menyediakan sintaks ringkas untuk mendeklarasikan konstruktor yang parameternya tersedia di mana saja dalam isi jenis.
Artikel ini menjelaskan cara mendeklarasikan konstruktor utama pada jenis Anda dan mengenali tempat menyimpan parameter konstruktor utama. Anda dapat memanggil konstruktor utama dari konstruktor lain dan menggunakan parameter konstruktor utama dalam anggota jenis tersebut.
Prasyarat
Memahami aturan untuk konstruktor utama
Anda dapat menambahkan parameter ke deklarasi struct atau class untuk membuat konstruktor utama . Parameter konstruktor utama berada dalam cakupan di seluruh definisi kelas. Penting untuk melihat parameter konstruktor utama sebagai parameter meskipun berada dalam cakupan di seluruh definisi kelas.
Beberapa aturan menjelaskan bahwa konstruktor-konstruktor tersebut dianggap sebagai parameter.
- Parameter konstruktor utama mungkin tidak disimpan jika tidak diperlukan.
- Parameter konstruktor utama bukan anggota kelas. Misalnya, parameter konstruktor utama bernama
paramtidak dapat diakses sebagaithis.param. - Parameter konstruktor utama dapat ditetapkan.
- Parameter konstruktor utama tidak menjadi properti, kecuali dalam tipe record.
Aturan ini adalah aturan yang sama yang sudah ditentukan untuk parameter ke metode apa pun, termasuk deklarasi konstruktor lainnya.
Berikut adalah penggunaan paling umum untuk parameter konstruktor utama:
- Diteruskan sebagai argumen ke
base()pemanggilan konstruktor - Menginisialisasi bidang atau properti anggota
- Rujuk parameter konstruktor pada anggota objek
Setiap konstruktor lain dari kelas harus memanggil konstruktor utama, secara langsung atau tidak langsung, melalui pemanggilan konstruktor dengan this(). Aturan ini memastikan bahwa parameter konstruktor utama ditetapkan di setiap bagian dari tubuh tipe.
Menginisialisasi properti atau bidang yang tidak dapat diubah
Kode berikut menginisialisasi dua properti readonly (tidak dapat diubah) yang dihitung dari parameter konstruktor utama:
public readonly struct Distance(double dx, double dy)
{
public readonly double Magnitude { get; } = Math.Sqrt(dx * dx + dy * dy);
public readonly double Direction { get; } = Math.Atan2(dy, dx);
}
Contoh ini menggunakan konstruktor utama untuk menginisialisasi properti readonly terhitung. Penginisialisasi bidang untuk properti Magnitude dan Direction menggunakan parameter konstruktor utama. Parameter konstruktor utama tidak digunakan di tempat lain dalam struktur. Kode membuat struct seolah-olah ditulis dengan cara berikut:
public readonly struct Distance
{
public readonly double Magnitude { get; }
public readonly double Direction { get; }
public Distance(double dx, double dy)
{
Magnitude = Math.Sqrt(dx * dx + dy * dy);
Direction = Math.Atan2(dy, dx);
}
}
Fitur ini memudahkan penggunaan penginisialisasi bidang saat Anda memerlukan argumen untuk menginisialisasi bidang atau properti.
Membuat status dapat diubah
Contoh sebelumnya menggunakan parameter konstruktor utama untuk menginisialisasi properti readonly. Anda juga dapat menggunakan konstruktor utama untuk properti yang tidak hanya untuk dibaca.
Pertimbangkan kode berikut:
public struct Distance(double dx, double dy)
{
public readonly double Magnitude => Math.Sqrt(dx * dx + dy * dy);
public readonly double Direction => Math.Atan2(dy, dx);
public void Translate(double deltaX, double deltaY)
{
dx += deltaX;
dy += deltaY;
}
public Distance() : this(0,0) { }
}
Dalam contoh ini, metode Translate mengubah dx dan dy komponen, yang memerlukan properti Magnitude dan Direction untuk dihitung saat diakses. Operator lambda (=>) menunjuk aksesor berbasis ekspresi get, sedangkan operator sama dengan (=) menunjuk penginisialisasi.
Versi kode ini menambahkan konstruktor tanpa parameter ke struktur. Konstruktor tanpa parameter harus memanggil konstruktor utama, yang memastikan semua parameter konstruktor utama diinisialisasi. Properti konstruktor utama diakses dalam metode, dan pengkompilasi membuat bidang tersembunyi untuk mewakili setiap parameter.
Kode berikut menunjukkan perkiraan tentang apa yang dihasilkan kompilator. Nama-nama bidang yang sebenarnya adalah pengidentifikasi Valid Common Intermediate Language (CIL), tetapi bukan pengidentifikasi yang valid untuk C#.
public struct Distance
{
private double __unspeakable_dx;
private double __unspeakable_dy;
public readonly double Magnitude => Math.Sqrt(__unspeakable_dx * __unspeakable_dx + __unspeakable_dy * __unspeakable_dy);
public readonly double Direction => Math.Atan2(__unspeakable_dy, __unspeakable_dx);
public void Translate(double deltaX, double deltaY)
{
__unspeakable_dx += deltaX;
__unspeakable_dy += deltaY;
}
public Distance(double dx, double dy)
{
__unspeakable_dx = dx;
__unspeakable_dy = dy;
}
public Distance() : this(0, 0) { }
}
Penyimpanan yang dibuat kompilator
Untuk contoh pertama di bagian ini, pengkompilasi tidak perlu membuat bidang untuk menyimpan nilai parameter konstruktor utama. Namun, dalam contoh kedua, parameter konstruktor utama digunakan di dalam metode, sehingga pengkompilasi harus membuat penyimpanan untuk parameter.
Pengkompilasi membuat penyimpanan untuk konstruktor utama apa pun hanya ketika parameter diakses dalam isi anggota jenis Anda. Jika tidak, parameter konstruktor utama tidak disimpan dalam objek.
Gunakan injeksi dependensi
Penggunaan umum lainnya untuk konstruktor utama adalah menentukan parameter untuk injeksi dependensi. Kode berikut membuat pengontrol sederhana yang memerlukan antarmuka layanan untuk penggunaannya:
public interface IService
{
Distance GetDistance();
}
public class ExampleController(IService service) : ControllerBase
{
[HttpGet]
public ActionResult<Distance> Get()
{
return service.GetDistance();
}
}
Konstruktor utama dengan jelas menunjukkan parameter yang diperlukan di kelas . Anda menggunakan parameter konstruktor utama seperti yang Anda lakukan pada variabel lain di kelas .
Menginisialisasi kelas dasar
Anda dapat memanggil konstruktor utama untuk kelas dasar dari konstruktor utama kelas turunan. Pendekatan ini adalah cara term mudah untuk menulis kelas turunan yang harus memanggil konstruktor utama di kelas dasar. Pertimbangkan hierarki kelas yang mewakili jenis rekening yang berbeda sebagai bank. Kode berikut menunjukkan seperti apa kelas dasarnya:
public class BankAccount(string accountID, string owner)
{
public string AccountID { get; } = accountID;
public string Owner { get; } = owner;
public override string ToString() => $"Account ID: {AccountID}, Owner: {Owner}";
}
Semua rekening bank, tidak peduli jenisnya, memiliki sifat-sifat untuk nomor rekening dan pemilik. Dalam aplikasi yang telah selesai, Anda dapat menambahkan fungsionalitas umum lainnya ke kelas dasar.
Banyak jenis memerlukan validasi yang lebih spesifik pada parameter konstruktor. Misalnya, BankAccount kelas memiliki persyaratan khusus untuk owner parameter dan accountID . Parameter owner tidak boleh null atau spasi kosong, dan accountID parameter harus berupa string yang berisi 10 digit. Anda dapat menambahkan validasi ini saat menetapkan properti yang sesuai:
public class BankAccount(string accountID, string owner)
{
public string AccountID { get; } = ValidAccountNumber(accountID)
? accountID
: throw new ArgumentException("Invalid account number", nameof(accountID));
public string Owner { get; } = string.IsNullOrWhiteSpace(owner)
? throw new ArgumentException("Owner name cannot be empty", nameof(owner))
: owner;
public override string ToString() => $"Account ID: {AccountID}, Owner: {Owner}";
public static bool ValidAccountNumber(string accountID) =>
accountID?.Length == 10 && accountID.All(c => char.IsDigit(c));
}
Contoh ini menunjukkan cara memvalidasi parameter konstruktor sebelum Anda menetapkannya ke properti. Anda dapat menggunakan metode bawaan seperti String.IsNullOrWhiteSpace(String) atau metode validasi Anda sendiri, seperti ValidAccountNumber. Dalam contoh ini, pengecualian apa pun dilemparkan oleh konstruktor ketika memanggil penginisialisasi. Jika parameter konstruktor tidak digunakan untuk menetapkan bidang, pengecualian apa pun akan dilemparkan saat parameter konstruktor pertama kali diakses.
Satu kelas turunan mungkin mewakili rekening giro.
public class CheckingAccount(string accountID, string owner, decimal overdraftLimit = 0) : BankAccount(accountID, owner)
{
public decimal CurrentBalance { get; private set; } = 0;
public void Deposit(decimal amount)
{
if (amount < 0)
{
throw new ArgumentOutOfRangeException(nameof(amount), "Deposit amount must be positive");
}
CurrentBalance += amount;
}
public void Withdrawal(decimal amount)
{
if (amount < 0)
{
throw new ArgumentOutOfRangeException(nameof(amount), "Withdrawal amount must be positive");
}
if (CurrentBalance - amount < -overdraftLimit)
{
throw new InvalidOperationException("Insufficient funds for withdrawal");
}
CurrentBalance -= amount;
}
public override string ToString() => $"Account ID: {AccountID}, Owner: {Owner}, Balance: {CurrentBalance}";
}
Kelas turunan CheckingAccount memiliki konstruktor utama yang mengambil semua parameter yang diperlukan di kelas dasar, dan parameter lain dengan nilai default. Konstruktor utama memanggil konstruktor dasar dengan : BankAccount(accountID, owner) sintaks. Ekspresi ini menentukan jenis untuk kelas dasar dan argumen untuk konstruktor utama.
Tidak wajib bagi kelas turunan Anda untuk menggunakan konstruktor utama. Anda dapat membuat konstruktor di kelas turunan yang memanggil konstruktor utama untuk kelas dasar, seperti yang ditunjukkan dalam contoh berikut:
public class LineOfCreditAccount : BankAccount
{
private readonly decimal _creditLimit;
public LineOfCreditAccount(string accountID, string owner, decimal creditLimit) : base(accountID, owner)
{
_creditLimit = creditLimit;
}
public decimal CurrentBalance { get; private set; } = 0;
public void Deposit(decimal amount)
{
if (amount < 0)
{
throw new ArgumentOutOfRangeException(nameof(amount), "Deposit amount must be positive");
}
CurrentBalance += amount;
}
public void Withdrawal(decimal amount)
{
if (amount < 0)
{
throw new ArgumentOutOfRangeException(nameof(amount), "Withdrawal amount must be positive");
}
if (CurrentBalance - amount < -_creditLimit)
{
throw new InvalidOperationException("Insufficient funds for withdrawal");
}
CurrentBalance -= amount;
}
public override string ToString() => $"{base.ToString()}, Balance: {CurrentBalance}";
}
Ada satu potensi kekhawatiran dengan hierarki kelas dan konstruktor utama. Dimungkinkan untuk membuat beberapa salinan parameter konstruktor utama karena parameter digunakan dalam kelas turunan dan dasar. Kode berikut membuat dua salinan masing-masing owner parameter dan accountID :
public class SavingsAccount(string accountID, string owner, decimal interestRate) : BankAccount(accountID, owner)
{
public SavingsAccount() : this("default", "default", 0.01m) { }
public decimal CurrentBalance { get; private set; } = 0;
public void Deposit(decimal amount)
{
if (amount < 0)
{
throw new ArgumentOutOfRangeException(nameof(amount), "Deposit amount must be positive");
}
CurrentBalance += amount;
}
public void Withdrawal(decimal amount)
{
if (amount < 0)
{
throw new ArgumentOutOfRangeException(nameof(amount), "Withdrawal amount must be positive");
}
if (CurrentBalance - amount < 0)
{
throw new InvalidOperationException("Insufficient funds for withdrawal");
}
CurrentBalance -= amount;
}
public void ApplyInterest()
{
CurrentBalance *= 1 + interestRate;
}
public override string ToString() => $"Account ID: {accountID}, Owner: {owner}, Balance: {CurrentBalance}";
}
Baris yang disorot dalam contoh ini menunjukkan bahwa ToString metode menggunakan parameter konstruktor utama (owner dan accountID) daripada properti kelas dasar (Owner dan AccountID). Hasilnya adalah bahwa kelas turunan, SavingsAccount, membuat penyimpanan untuk salinan parameter. Salinan di kelas turunan berbeda dari properti di kelas dasar. Jika properti kelas dasar dapat dimodifikasi, instans kelas turunan tidak melihat modifikasi. Kompilator mengeluarkan peringatan untuk parameter konstruktor utama yang digunakan dalam kelas turunan dan diteruskan ke konstruktor kelas dasar. Dalam hal ini, perbaikannya adalah menggunakan properti kelas dasar.