Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
C# 12 zavádí primární konstruktory, které poskytují stručnou syntaxi pro deklaraci konstruktorů, jejichž parametry jsou k dispozici kdekoli v těle typu.
Tento článek popisuje, jak deklarovat primární konstruktor ve vašem typu a rozpoznat, kam se mají ukládat parametry primárního konstruktoru. Primární konstruktory můžete volat z jiných konstruktorů a parametry primárního konstruktoru použít ve vlastnostech typu.
Požadavky
- Nejnovější sada .NET SDK
- editor Visual Studio Code editoru
- C# DevKit
Porozumět pravidlům pro primární konstruktory
Do deklarace struct nebo class můžete přidat parametry pro vytvoření primárního konstruktoru. Parametry primárního konstruktoru jsou dostupné v průběhu celé definice třídy. Parametry primárního konstruktoru je důležité chápat jako parametry , i když jsou v platnosti v rámci definice třídy.
Několik pravidel objasňuje, že tyto konstruktory jsou parametry:
- Parametry primárního konstruktoru nemusí být uloženy, pokud nejsou potřeba.
- Parametry primárního konstruktoru nejsou členy třídy. Například k primárnímu parametru konstruktoru s názvem
paramnení možné přistupovat jakothis.param. - Primární parametry konstruktoru lze přiřadit.
- Parametry primárního konstruktoru se nestanou vlastnostmi, s výjimkou záznamových typů .
Tato pravidla jsou stejná pravidla, která jsou již definována pro parametry jakékoli metody, včetně jiných deklarací konstruktoru.
Tady jsou nejběžnější použití parametru primárního konstruktoru:
- Předání argumentu volání konstruktoru
base() - Inicializovat členské pole nebo vlastnost
- Odkazovat na parametr konstruktoru v členu instance
Každý další konstruktor třídy musí volat primární konstruktor, a to přímo nebo nepřímo, prostřednictvím vyvolání konstruktoru this(). Toto pravidlo zajišťuje, aby se parametry primárního konstruktoru přiřadily všude v těle typu.
Inicializace neměnných vlastností nebo polí
Následující kód inicializuje dvě vlastnosti jen pro čtení (neměnné), které se počítají z parametrů primárního konstruktoru:
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);
}
Tento příklad používá primární konstruktor k inicializaci výpočtových vlastností readonly. Inicializátory polí pro vlastnosti Magnitude a Direction používají parametry primárního konstruktoru. Parametry primárního konstruktoru se ve struktuře nepoužívají nikde jinde. Kód vytvoří strukturu, jako by byla napsána následujícím způsobem:
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);
}
}
Tato funkce usnadňuje použití inicializátorů polí v případě, že potřebujete argumenty k inicializaci pole nebo vlastnosti.
Vytvoření proměnlivých stavů
Předchozí příklady používají parametry primárního konstruktoru k inicializaci vlastností jen pro čtení. Primární konstruktory můžete použít také pro vlastnosti, které nejsou jen pro čtení.
Vezměte v úvahu následující kód:
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) { }
}
V tomto příkladu metoda Translate změní komponenty dx a dy, které vyžadují, aby se při přístupu počítaly vlastnosti Magnitude a Direction. Operátor lambda (=>) určuje přístupový objekt s body výrazu get , zatímco operátor equal-to (=) označuje inicializátor.
Tato verze kódu přidá konstruktor bez parametrů do struktury. Konstruktor bez parametrů musí vyvolat primární konstruktor, který zajišťuje inicializaci všech parametrů primárního konstruktoru. Vlastnosti primárního konstruktoru jsou přístupné v metodě a kompilátor vytvoří skrytá pole představující každý parametr.
Následující kód ukazuje aproximaci toho, co kompilátor generuje. Skutečné názvy polí jsou platné identifikátory CIL (Common Intermediate Language), ale nejsou platné identifikátory jazyka 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) { }
}
Úložiště vytvořené kompilátorem
V prvním příkladu v této části kompilátor nemusel vytvořit pole pro uložení hodnoty parametrů primárního konstruktoru. V druhém příkladu se však primární parametr konstruktoru používá uvnitř metody, takže kompilátor musí vytvořit úložiště pro parametry.
Kompilátor vytvoří úložiště pro všechny primární konstruktory pouze v případě, že je parametr přístupný v těle člena vašeho typu. V opačném případě nejsou parametry primárního konstruktoru uloženy v objektu.
Injektování závislostí
Dalším běžným použitím primárních konstruktorů je zadání parametrů pro injektáž závislostí. Následující kód vytvoří jednoduchý kontroler, který vyžaduje rozhraní služby pro jeho použití:
public interface IService
{
Distance GetDistance();
}
public class ExampleController(IService service) : ControllerBase
{
[HttpGet]
public ActionResult<Distance> Get()
{
return service.GetDistance();
}
}
Primární konstruktor jasně uvádí parametry potřebné pro třídu. Parametry primárního konstruktoru použijete stejně jako jakoukoli jinou proměnnou ve třídě.
Inicializace základní třídy
Primární konstruktor základní třídy můžete vyvolat z primárního konstruktoru odvozené třídy. Tento přístup je nejjednodušší způsob, jak napsat odvozenou třídu, která musí vyvolat primární konstruktor v základní třídě. Představte si hierarchii tříd, které představují různé typy účtů jako banku. Následující kód ukazuje, jak může základní třída vypadat:
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}";
}
Všechny bankovní účty bez ohledu na typ mají vlastnosti pro číslo účtu a vlastníka. V dokončené aplikaci můžete do základní třídy přidat další běžné funkce.
Mnoho typů vyžaduje konkrétnější ověřování u parametrů konstruktoru. Například třída BankAccount má specifické požadavky na parametry owner a accountID. Parametr owner nesmí být null ani prázdné znaky a parametr accountID musí být řetězec obsahující 10 číslic. Toto ověření můžete přidat při přiřazování odpovídajících vlastností:
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));
}
Tento příklad ukazuje, jak ověřit parametry konstruktoru před jejich přiřazením k vlastnostem. Můžete použít předdefinované metody, jako je String.IsNullOrWhiteSpace(String) nebo vlastní metoda ověřování, například ValidAccountNumber. V příkladu jsou jakékoli výjimky vyvolány z konstruktoru, když ten zavolá inicializátory. Pokud se k přiřazení pole nepoužívá parametr konstruktoru, při prvním přístupu k parametru konstruktoru se vyvolá všechny výjimky.
Jedna odvozená třída může představovat kontrolní účet:
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}";
}
Odvozená CheckingAccount třída má primární konstruktor, který přebírá všechny parametry potřebné v základní třídě a další parametr s výchozí hodnotou. Primární konstruktor volá základní konstruktor se syntaxí : BankAccount(accountID, owner). Tento výraz určuje typ základní třídy i argumenty primárního konstruktoru.
Vaše odvozená třída nemusí použít primární konstruktor. V odvozené třídě můžete vytvořit konstruktor, který vyvolá primární konstruktor základní třídy, jak je znázorněno v následujícím příkladu:
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}";
}
Existuje jeden potenciální problém s hierarchiemi tříd a primárními konstruktory. Je možné vytvořit více kopií parametru primárního konstruktoru, protože parametr se používá v odvozených i základních třídách. Následující kód vytvoří dvě kopie každého z owner a accountID parametrů:
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}";
}
Zvýrazněný řádek v tomto příkladu ukazuje, že metoda ToString používá primární konstruktor parametry (owner a accountID) místo vlastností základní třídy (Owner a AccountID). Výsledkem je, že odvozená třída, SavingsAccount, vytvoří úložiště pro kopie parametrů. Kopie v odvozené třídě se liší od vlastnosti v základní třídě. Pokud lze upravit vlastnost základní třídy, instance odvozené třídy nevidí změnu. Kompilátor vydává upozornění pro primární parametry konstruktoru, které se používají v odvozené třídě a předané konstruktoru základní třídy. V tomto případě je řešením využití vlastností základní třídy.