Aracılığıyla paylaş


Sınıflar ve yapılar için birincil oluşturucuları bildirme

C# 12, parametreleri türün gövdesinde herhangi bir yerde bulunan oluşturucuları bildirmek için kısa bir söz dizimi sağlayan birincil oluşturucuları tanıtır.

Bu makalede, türünüzde bir birincil oluşturucu bildirme ve birincil oluşturucu parametrelerinin depolandığı yeri tanıma açıklanmaktadır. Diğer oluşturuculardan birincil oluşturucuları çağırabilir ve türün üyelerinde birincil oluşturucu parametrelerini kullanabilirsiniz.

Önkoşullar

Birincil oluşturucuların kurallarını anlama

Birincil oluşturucu oluşturmak için veya structclass bildirimine parametreler ekleyebilirsiniz. Birincil oluşturucu parametreleri, sınıf tanımı boyunca kapsam içindedir. Birincil oluşturucu parametrelerini, sınıf tanımı boyunca kapsam içinde olsalar bile parametre olarak görüntülemek önemlidir.

Çeşitli kurallar, bu oluşturucuların parametre olduğunu netleştirmektedir:

  • Birincil oluşturucu parametreleri gerekli değilse depolanamayabilir.
  • Birincil oluşturucu parametreleri sınıfın üyesi değildir. Örneğin, adlı param birincil oluşturucu parametresine olarak this.paramerişilemiyor.
  • Birincil oluşturucu parametrelerine atanabilir.
  • Birincil oluşturucu parametreleri, kayıt türleri dışında özellik haline gelmez.

Bu kurallar, diğer oluşturucu bildirimleri de dahil olmak üzere herhangi bir yönteme yönelik parametreler için önceden tanımlanmış olan kurallarla aynıdır.

Birincil oluşturucu parametresinin en yaygın kullanımları şunlardır:

  • Oluşturucu çağrısına base() bağımsız değişken olarak geçirme
  • Üye alanı veya özelliği başlatma
  • Örnek üyesinde oluşturucu parametresine başvurma

Bir sınıfın diğer tüm oluşturucuları, bir this() oluşturucu çağırma yoluyla doğrudan veya dolaylı olarak birincil oluşturucuyu çağırmalıdır. Bu kural, birincil oluşturucu parametrelerinin türün gövdesindeki her yere atanmasını sağlar.

Sabit özellikleri veya alanları başlatma

Aşağıdaki kod, birincil oluşturucu parametrelerinden hesaplanan iki salt okunur (sabit) özelliği başlatır:

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);
}

Bu örnekte, hesaplanan salt okunur özellikleri başlatmak için bir birincil oluşturucu kullanılır. ve Direction özellikleri için Magnitude alan başlatıcıları birincil oluşturucu parametrelerini kullanır. Birincil oluşturucu parametreleri yapıda başka hiçbir yerde kullanılmaz. Kod, aşağıdaki şekilde yazılmış gibi bir yapı oluşturur:

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);
    }
}

Bu özellik, bir alanı veya özelliği başlatmak için bağımsız değişkenlere ihtiyacınız olduğunda alan başlatıcılarını kullanmayı kolaylaştırır.

Değiştirilebilir durum oluşturma

Önceki örneklerde salt okunur özellikleri başlatmak için birincil oluşturucu parametreleri kullanılır. Salt okunur olmayan özellikler için birincil oluşturucuları da kullanabilirsiniz.

Aşağıdaki kodu inceleyin:

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) { }
}

Bu örnekte yöntemi, Translate erişildiğinde ve dy özelliklerinin hesaplanması gereken Magnitude ve Direction bileşenlerini değiştirirdx. Lambda işleci (=>) ifade gövdeli get bir erişimci belirlerken, eşittir işleci (=) bir başlatıcı belirler.

Kodun bu sürümü yapıya parametresiz bir oluşturucu ekler. Parametresiz oluşturucu, tüm birincil oluşturucu parametrelerinin başlatılmasını sağlayan birincil oluşturucuyu çağırmalıdır. Birincil oluşturucu özelliklerine bir yöntemde erişilir ve derleyici her parametreyi temsil etmek için gizli alanlar oluşturur.

Aşağıdaki kod, derleyicinin ne oluşturduğuna ilişkin yaklaşık bilgileri gösterir. Gerçek alan adları geçerli Ortak Ara Dil (CIL) tanımlayıcılarıdır, ancak geçerli C# tanımlayıcıları değildir.

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) { }
}

Derleyici tarafından oluşturulan depolama

Bu bölümdeki ilk örnekte, derleyicinin birincil oluşturucu parametrelerinin değerini depolamak için bir alan oluşturması gerekmiyordu. Ancak ikinci örnekte birincil oluşturucu parametresi bir yöntemin içinde kullanıldığından derleyicinin parametreler için depolama alanı oluşturması gerekir.

Derleyici, birincil oluşturucular için yalnızca parametreye türünüzün bir üyesinin gövdesinde erişildiğinde depolama alanı oluşturur. Aksi takdirde, birincil oluşturucu parametreleri nesnesinde depolanmaz.

Bağımlılık enjeksiyonunu kullan

Birincil oluşturucular için bir diğer yaygın kullanım da bağımlılık ekleme parametrelerini belirtmektir. Aşağıdaki kod, kullanımı için bir hizmet arabirimi gerektiren basit bir denetleyici oluşturur:

public interface IService
{
    Distance GetDistance();
}

public class ExampleController(IService service) : ControllerBase
{
    [HttpGet]
    public ActionResult<Distance> Get()
    {
        return service.GetDistance();
    }
}

Birincil oluşturucu, sınıfında gereken parametreleri açıkça gösterir. Birincil oluşturucu parametrelerini sınıftaki diğer değişkenler gibi kullanırsınız.

Temel sınıfı başlatma

Türetilmiş sınıfın birincil oluşturucusundan bir temel sınıf için birincil oluşturucuyu çağırabilirsiniz. Bu yaklaşım, temel sınıfta birincil oluşturucuyu çağırması gereken türetilmiş bir sınıf yazmanın en kolay yoludur. Farklı hesap türlerini banka olarak temsil eden sınıf hiyerarşisini düşünün. Aşağıdaki kod, temel sınıfın nasıl görünebileceğini gösterir:

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}";
}

Türü ne olursa olsun tüm banka hesapları, hesap numarası ve sahibi için özelliklere sahiptir. Tamamlanan uygulamada, temel sınıfa diğer ortak işlevleri ekleyebilirsiniz.

Birçok tür oluşturucu parametrelerinde daha belirgin doğrulama gerektirir. Örneğin, sınıfı ve BankAccount parametreleri için belirli gereksinimlere owneraccountID sahiptir. owner parametresi veya boşluk olmamalıdır null ve accountID parametre 10 basamak içeren bir dize olmalıdır. Karşılık gelen özellikleri atarken bu doğrulamayı ekleyebilirsiniz:

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));
}

Bu örnek, oluşturucu parametrelerini özelliklere atamadan önce nasıl doğrulanabileceğinizi gösterir. gibi String.IsNullOrWhiteSpace(String) yerleşik yöntemleri veya gibi ValidAccountNumberkendi doğrulama yönteminizi kullanabilirsiniz. Örnekte, başlatıcıları çağırdığında oluşturucudan herhangi bir özel durum oluşturulur. Bir alan atamak için oluşturucu parametresi kullanılmazsa, oluşturucu parametresine ilk kez erişildiğinde özel durumlar oluşur.

Türetilmiş bir sınıf bir çek hesabını temsil edebilir:

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}";
}

Türetilmiş CheckingAccount sınıf, temel sınıfta gereken tüm parametreleri alan bir birincil oluşturucuya ve varsayılan değere sahip başka bir parametreye sahiptir. Birincil oluşturucu, söz dizimi ile temel oluşturucuyu : BankAccount(accountID, owner) çağırır. Bu ifade hem temel sınıfın türünü hem de birincil oluşturucunun bağımsız değişkenlerini belirtir.

Türetilmiş sınıfınız birincil oluşturucu kullanmak için gerekli değildir. Aşağıdaki örnekte gösterildiği gibi, türetilmiş sınıfta temel sınıf için birincil oluşturucuyu çağıran bir oluşturucu oluşturabilirsiniz:

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}";
}

Sınıf hiyerarşileri ve birincil oluşturucularla ilgili olası bir sorun vardır. Parametre hem türetilmiş hem de temel sınıflarda kullanıldığından birincil oluşturucu parametresinin birden çok kopyasını oluşturmak mümkündür. Aşağıdaki kod ve accountID parametrelerinin her biri owner iki kopya oluşturur:

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}";
}

Bu örnekte vurgulanan satır, yönteminin ToStringtemel sınıf özellikleri ( ve accountID) yerine birincil oluşturucu parametrelerini (ownerOwner ve AccountID) kullandığını gösterir. Sonuç olarak, türetilmiş sınıfı, SavingsAccountparametre kopyaları için depolama oluşturur. Türetilmiş sınıftaki kopya, temel sınıftaki özelliğinden farklıdır. Temel sınıf özelliği değiştirilebilirse, türetilmiş sınıfın örneği değişikliği görmez. Derleyici, türetilmiş bir sınıfta kullanılan ve bir temel sınıf oluşturucusunda geçirilen birincil oluşturucu parametreleri için bir uyarı oluşturur. Bu örnekte, düzeltme temel sınıfın özelliklerini kullanmaktır.