Megjegyzés
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhat bejelentkezni vagy módosítani a címtárat.
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhatja módosítani a címtárat.
A C# 12 bemutatja elsődleges konstruktorokat, amelyek tömör szintaxissal deklarálják azokat a konstruktorokat, amelyek paraméterei a típus törzsében bárhol elérhetők.
Ez a cikk bemutatja, hogyan deklarálhat egy elsődleges konstruktort a típuson, és hogyan ismerheti fel az elsődleges konstruktorparaméterek tárolásának helyét. Meghívhat elsődleges konstruktorokat más konstruktorokból, és használhat elsődleges konstruktorparamétereket a típus tagjaiban.
Előfeltételek
- A legújabb .NET SDK
- Visual Studio Code szerkesztő
- A C# DevKit
Az elsődleges konstruktorok szabályainak ismertetése
Paramétereket adhat hozzá egy struct vagy class deklarációhoz egy elsődleges konstruktorlétrehozásához. Az elsődleges konstruktorparaméterek hatókörben vannak az osztálydefinícióban. Fontos, hogy az elsődleges konstruktorparamétereket paraméterekként kezeljük, annak ellenére, hogy az osztálydefiníció során is hatókörben vannak.
Számos szabály tisztázza, hogy ezek a konstruktorok paraméterek:
- Előfordulhat, hogy az elsődleges konstruktorparaméterek nem lesznek tárolva, ha nincs rájuk szükség.
- Az elsődleges konstruktorparaméterek nem tagjai az osztálynak. Például egy
paramnevű elsődleges konstruktorparaméter nem érhető elthis.parammódon. - Elsődleges konstruktorparaméterek rendelhetők hozzá.
- Az elsődleges konstruktorparaméterek nem válnak tulajdonságokká, kivéve rekord típusokban.
Ezek a szabályok ugyanazok a szabályok, amelyek már definiálva vannak a paraméterekhez bármely metódushoz, beleértve az egyéb konstruktor-deklarációkat is.
Az elsődleges konstruktorparaméter leggyakoribb felhasználási módjai a következők:
- Adjon át egy
base()konstruktor-meghívást argumentumként - Tagmező vagy tulajdonság inicializálása
- Hivatkozás a konstruktorparaméterre egy példánytagban
Az osztály minden más konstruktorának közvetlenül vagy közvetve kell meghívnia az elsődleges konstruktort egy this() konstruktor meghívásán keresztül. Ez a szabály biztosítja, hogy az elsődleges konstruktorparaméterek mindenhol hozzá legyenek rendelve a típus törzsében.
Nem módosítható tulajdonságok vagy mezők inicializálása
A következő kód inicializál két olvasható (nem módosítható) tulajdonságot, amelyek az elsődleges konstruktorparaméterekből vannak kiszámítva:
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);
}
Ez a példa egy elsődleges konstruktor használatával inicializálja a számított, csak olvasható tulajdonságokat. A Magnitude és Direction tulajdonságok mező inicializálói az elsődleges konstruktorparamétereket használják. Az elsődleges konstruktorparamétereket a rendszer sehol máshol nem használja a szerkezetben. A kód úgy hoz létre egy szerkezetet, mintha az a következő módon lett volna megírva:
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);
}
}
Ez a funkció megkönnyíti a mező inicializálóinak használatát, ha argumentumokra van szüksége egy mező vagy tulajdonság inicializálásához.
Mutable állapot létrehozása
Az előző példák elsődleges konstruktorparaméterekkel inicializálják az olvasható tulajdonságokat. Az elsődleges konstruktorokat olyan tulajdonságokhoz is használhatja, amelyek nem olvashatók.
Vegye figyelembe a következő kódot:
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) { }
}
Ebben a példában a Translate metódus módosítja a dx és dy összetevőket, amelyhez a Magnitude és Direction tulajdonságokat kell kiszámítani, amikor azokat elérik. A lambda operátor (=>) kifejezési testű get tartozékot jelöl ki, míg az egyenlő operátor (=) egy inicializálót jelöl ki.
A kód ezen verziója egy paraméter nélküli konstruktort ad hozzá a szerkezethez. A paraméter nélküli konstruktornak meg kell hívnia az elsődleges konstruktort, amely biztosítja az összes elsődleges konstruktorparaméter inicializálását. Az elsődleges konstruktor tulajdonságai egy metódusban érhetők el, és a fordító rejtett mezőket hoz létre az egyes paraméterek megjelenítéséhez.
Az alábbi kód bemutatja a fordító által generált adatok közelítését. A tényleges mezőnevek érvényes közbülső nyelv (CIL) azonosítók, de nem érvényes C# azonosítók.
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) { }
}
Fordító által létrehozott tároló
Ebben a szakaszban az első példában a fordítónak nem kellett mezőt létrehoznia az elsődleges konstruktorparaméterek értékének tárolásához. A második példában azonban az elsődleges konstruktorparamétert egy metóduson belül használja a rendszer, ezért a fordítónak tárolót kell létrehoznia a paraméterek számára.
A fordító csak akkor hoz létre tárolót az elsődleges konstruktorok számára, ha a paraméter az Ön típusának egy tagja törzsében érhető el. Ellenkező esetben az elsődleges konstruktorparaméterek nincsenek tárolva az objektumban.
Függőséginjektálás használata
Az elsődleges konstruktorok másik gyakori használata a függőséginjektálás paramétereinek megadása. Az alábbi kód egy egyszerű vezérlőt hoz létre, amely használatához szolgáltatásfelületre van szükség:
public interface IService
{
Distance GetDistance();
}
public class ExampleController(IService service) : ControllerBase
{
[HttpGet]
public ActionResult<Distance> Get()
{
return service.GetDistance();
}
}
Az elsődleges konstruktor egyértelműen jelzi az osztályban szükséges paramétereket. Az elsődleges konstruktorparamétereket ugyanúgy használja, mint az osztály bármely más változója.
Alaposztály inicializálása
Egy alaposztály elsődleges konstruktorát meghívhatja a származtatott osztály elsődleges konstruktorából. Ez a módszer a legegyszerűbb módja annak, hogy olyan származtatott osztályt írjon, amely meg kell hívnia egy elsődleges konstruktort az alaposztályban. Vegye figyelembe a különböző számlatípusokat bankként képviselő osztályok hierarchiáját. Az alábbi kód az alaposztály megjelenését mutatja be:
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}";
}
Minden bankszámla típustól függetlenül rendelkezik a számlaszám és a tulajdonos tulajdonságaival. A kész alkalmazásban más általános funkciókat is hozzáadhat az alaposztályhoz.
Számos típushoz pontosabb ellenőrzés szükséges a konstruktorparamétereken. A BankAccount osztály például a owner és accountID paraméterekre vonatkozó speciális követelményekkel rendelkezik. A owner paraméter nem lehet null vagy szóköz, és a accountID paraméternek 10 számjegyet tartalmazó sztringnek kell lennie. Ezt az ellenőrzést a megfelelő tulajdonságok hozzárendelésekor veheti fel:
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));
}
Ez a példa bemutatja, hogyan érvényesítheti a konstruktorparamétereket, mielőtt hozzárendeli őket a tulajdonságokhoz. Használhat beépített módszereket, például String.IsNullOrWhiteSpace(String) vagy saját érvényesítési módszert, például ValidAccountNumber. A példában a konstruktor kivételeket ad ki, amikor meghívja az inicializálókat. Ha egy konstruktorparaméter nem használható mező hozzárendelésére, a rendszer kivételeket alkalmaz a konstruktorparaméter első elérésekor.
Egy származtatott osztály egy ellenőrző fiókot jelölhet:
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}";
}
A származtatott CheckingAccount osztály rendelkezik egy elsődleges konstruktorral, amely az alaposztályban szükséges összes paramétert és egy másik alapértelmezett értéket tartalmazó paramétert használ. Az elsődleges konstruktor a : BankAccount(accountID, owner) szintaxissal hívja meg az alapkonstruktort. Ez a kifejezés az alaposztály típusát és az elsődleges konstruktor argumentumait is meghatározza.
A származtatott osztály számára nem kötelező az elsődleges konstruktor használata. Létrehozhat egy konstruktort a származtatott osztályban, amely meghívja az alaposztály elsődleges konstruktorát az alábbi példában látható módon:
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}";
}
Az osztályhierarchiákkal és az elsődleges konstruktorokkal kapcsolatban van egy lehetséges probléma. Az elsődleges konstruktorparaméter több példánya is létrehozható, mert a paraméter származtatott és alaposztályokban is használható. Az alábbi kód két példányt hoz létre a owner és accountID paraméterekből:
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}";
}
A példában kiemelt sor azt mutatja, hogy a ToString metódus az elsődleges konstruktorparamétereket (owner és accountID) használja a alaposztály tulajdonságai (Owner és AccountID) helyett. Az eredmény az, hogy a származtatott osztály (SavingsAccount) tárolót hoz létre a paraméterpéldányokhoz. A származtatott osztály másolata eltér az alaposztályban lévő tulajdonságtól. Ha az alaposztály tulajdonsága módosítható, a származtatott osztály példánya nem látja a módosítást. A fordító figyelmeztetést ad ki a származtatott osztályban használt és egy alaposztály-konstruktornak átadott elsődleges konstruktorparaméterekre vonatkozóan. Ebben az esetben a javítás az alaposztály tulajdonságainak használata.