Tutorial: Menjelajahi konstruktor utama
C# 12 memperkenalkan konstruktor utama, sintaks ringkas untuk mendeklarasikan konstruktor yang parameternya tersedia di mana saja dalam isi jenis.
Dalam tutorial ini, Anda akan mempelajari:
- Kapan harus mendeklarasikan konstruktor utama pada jenis Anda
- Cara memanggil konstruktor utama dari konstruktor lain
- Cara menggunakan parameter konstruktor utama dalam anggota jenis
- Tempat parameter konstruktor utama disimpan
Prasyarat
Anda perlu menyiapkan komputer Anda untuk menjalankan .NET 8 atau yang lebih baru, termasuk pengkompilasi C# 12 atau yang lebih baru. Pengkompilasi C# 12 tersedia dimulai dengan Visual Studio 2022 versi 17.7 atau .NET 8 SDK.
Konstruktor utama
Anda dapat menambahkan parameter ke struct
deklarasi 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 mengklarifikasi bahwa aturan tersebut adalah parameter:
- Parameter konstruktor utama mungkin tidak disimpan jika tidak diperlukan.
- Parameter konstruktor utama bukan anggota kelas. Misalnya, parameter konstruktor utama bernama
param
tidak dapat diakses sebagaithis.param
. - Parameter konstruktor utama dapat ditetapkan.
- Parameter konstruktor utama tidak menjadi properti, kecuali dalam
record
jenis.
Aturan ini sama dengan parameter untuk metode apa pun, termasuk deklarasi konstruktor lainnya.
Penggunaan yang paling umum untuk parameter konstruktor utama adalah:
- Sebagai argumen untuk
base()
pemanggilan konstruktor. - Untuk menginisialisasi bidang atau properti anggota.
- Mereferensikan parameter konstruktor dalam anggota instans.
Setiap konstruktor lain untuk kelas harus memanggil konstruktor utama, secara langsung atau tidak langsung, melalui this()
pemanggilan konstruktor. Aturan tersebut memastikan bahwa parameter konstruktor utama ditetapkan di mana saja dalam isi jenis.
Menginisialisasi properti
Kode berikut menginisialisasi dua properti readonly 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);
}
Kode sebelumnya menunjukkan konstruktor utama yang digunakan untuk menginisialisasi properti readonly terhitung. Penginisialisasi bidang untuk Magnitude
dan Direction
menggunakan parameter konstruktor utama. Parameter konstruktor utama tidak digunakan di tempat lain dalam struktur. Struktur sebelumnya adalah seolah-olah Anda akan menulis kode 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 baru 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 saat properti tidak readonly. Pertimbangkan gambar 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 sebelumnya, metode mengubah Translate
dx
komponen dan dy
. Itu mengharuskan Magnitude
properti dan Direction
dihitung saat diakses. Operator =>
menunjuk aksesor bertubuh get
ekspresi, sedangkan =
operator menunjuk penginisialisasi. Versi ini menambahkan konstruktor tanpa parameter ke struct. Konstruktor tanpa parameter harus memanggil konstruktor utama, sehingga semua parameter konstruktor utama diinisialisasi.
Dalam contoh sebelumnya, properti konstruktor utama diakses dalam metode . Oleh karena itu, pengkompilasi membuat bidang tersembunyi untuk mewakili setiap parameter. Kode berikut menunjukkan kira-kira apa yang dihasilkan kompilator. Nama bidang aktual adalah pengidentifikasi CIL yang valid, tetapi pengidentifikasi C# tidak valid.
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) { }
}
Penting untuk dipahami bahwa contoh pertama tidak mengharuskan pengkompilasi membuat bidang untuk menyimpan nilai parameter konstruktor utama. Contoh kedua menggunakan parameter konstruktor utama di dalam metode, dan oleh karena itu memerlukan pengkompilasi untuk membuat penyimpanan untuk mereka. Pengkompilasi membuat penyimpanan untuk konstruktor utama apa pun hanya ketika parameter tersebut diakses dalam isi anggota jenis Anda. Jika tidak, parameter konstruktor utama tidak disimpan dalam objek.
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 kelas dasar dari konstruktor utama kelas turunan. Ini adalah cara term mudah bagi Anda untuk menulis kelas turunan yang harus memanggil konstruktor utama di kelas dasar. Misalnya, pertimbangkan hierarki kelas yang mewakili jenis akun yang berbeda sebagai bank. Kelas dasar akan terlihat seperti kode berikut:
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, terlepas dari jenisnya, memiliki properti untuk nomor rekening dan pemilik. Dalam aplikasi yang telah selesai, fungsionalitas umum lainnya akan ditambahkan ke kelas dasar.
Banyak jenis memerlukan validasi yang lebih spesifik pada parameter konstruktor. Misalnya, BankAccount
memiliki persyaratan khusus untuk owner
parameter dan accountID
: owner
tidak boleh null
atau spasi kosong, dan accountID
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 sebelumnya menunjukkan bagaimana Anda dapat memvalidasi parameter konstruktor sebelum menetapkannya ke properti. Anda dapat menggunakan metode bawaan, seperti String.IsNullOrWhiteSpace(String), atau metode validasi Anda sendiri, seperti ValidAccountNumber
. Dalam contoh sebelumnya, pengecualian apa pun dilemparkan dari 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 akan menunjukkan akun pemeriksaan:
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 menggunakan sintaks.: BankAccount(accountID, owner)
Ekspresi ini menentukan jenis untuk kelas dasar, dan argumen untuk konstruktor utama.
Kelas turunan Anda tidak diperlukan untuk menggunakan konstruktor utama. Anda dapat membuat konstruktor di kelas turunan yang memanggil konstruktor utama 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 masalah potensial dengan hierarki kelas dan konstruktor utama: dimungkinkan untuk membuat beberapa salinan parameter konstruktor utama karena digunakan dalam kelas turunan dan dasar. Contoh kode berikut membuat dua salinan masing-masing bidang owner
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 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 tersebut. Salinan di kelas turunan berbeda dari properti di kelas dasar. Jika properti kelas dasar dapat dimodifikasi, instans kelas turunan tidak akan melihat modifikasi tersebut. 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.
Ringkasan
Anda dapat menggunakan konstruktor utama yang paling sesuai dengan desain Anda. Untuk kelas dan struktur, parameter konstruktor utama adalah parameter untuk konstruktor yang harus dipanggil. Anda dapat menggunakannya untuk menginisialisasi properti. Anda dapat menginisialisasi bidang. Properti atau bidang tersebut dapat tidak dapat diubah, atau dapat diubah. Anda dapat menggunakannya dalam metode. Parameternya adalah parameter, dan Anda menggunakannya dengan cara apa yang paling sesuai dengan desain Anda. Anda dapat mempelajari selengkapnya tentang konstruktor utama di artikel panduan pemrograman C# tentang konstruktor instans dan spesifikasi konstruktor utama yang diusulkan.
Saran dan Komentar
https://aka.ms/ContentUserFeedback.
Segera hadir: Sepanjang tahun 2024 kami akan menghentikan penggunaan GitHub Issues sebagai mekanisme umpan balik untuk konten dan menggantinya dengan sistem umpan balik baru. Untuk mengetahui informasi selengkapnya, lihat:Kirim dan lihat umpan balik untuk