Öğretici: Birincil oluşturucuları keşfetme

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

Bu öğreticide şunları öğreneceksiniz:

  • Türünüzde bir birincil oluşturucu ne zaman bildirebilirsiniz?
  • Diğer oluşturuculardan birincil oluşturucuları çağırma
  • Türün üyelerinde birincil oluşturucu parametrelerini kullanma
  • Birincil oluşturucu parametrelerinin depolandığı yer

Önkoşullar

C# 12 veya üzeri derleyici de dahil olmak üzere makinenizi .NET 8 veya üzerini çalıştıracak şekilde ayarlamanız gerekir. C# 12 derleyicisi, Visual Studio 2022 sürüm 17.7 veya .NET 8 SDK'dan başlayarak kullanılabilir.

Birincil oluşturucular

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 bunların parametre olduğunu net bir şekilde belirtir:

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

Bu kurallar, diğer oluşturucu bildirimleri de dahil olmak üzere herhangi bir yöntemin parametreleriyle aynıdır.

Birincil oluşturucu parametresi için en yaygın kullanım alanları şunlardır:

  1. Oluşturucu çağrısına base() bağımsız değişken olarak.
  2. Bir üye alanı veya özelliği başlatmak için.
  3. Ö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övdesinde herhangi bir yere atanmasını sağlar.

Initialize özelliği

Aşağıdaki kod, birincil oluşturucu parametrelerinden hesaplanan iki salt okunur ö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);
}

Yukarıdaki kod, hesaplanan salt okunur özellikleri başlatmak için kullanılan bir birincil oluşturucuyu gösterir. için alan başlatıcıları Magnitude ve Direction birincil oluşturucu parametrelerini kullanır. Birincil oluşturucu parametreleri yapıda başka hiçbir yerde kullanılmaz. Yukarıdaki yapı, aşağıdaki kodu yazmışsınız gibi olur:

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

Yeni ö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

Yukarıdaki örneklerde salt okunur özellikleri başlatmak için birincil oluşturucu parametreleri kullanılır. Ayrıca, özellikler salt okunur olmadığında 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) { }
}

Yukarıdaki örnekte yöntemi ve Translatedy bileşenlerini değiştirirdx. Bu, erişildiğinde ve Direction özelliklerinin hesaplanması gerekirMagnitude. => işleci ifade gövdeli get bir erişimci belirlerken=, işleç bir başlatıcı belirler. Bu sürüm, yapıya parametresiz bir oluşturucu ekler. Parametresiz oluşturucu birincil oluşturucuyu çağırmalıdır, böylece tüm birincil oluşturucu parametreleri başlatılır.

Önceki örnekte, birincil oluşturucu özelliklerine bir yöntemde erişilir. Bu nedenle, derleyici her parametreyi temsil etmek için gizli alanlar oluşturur. Aşağıdaki kod, derleyicinin yaklaşık olarak ne ürettiğini gösterir. Gerçek alan adları geçerli 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) { }
}

İlk örneğin, derleyicinin birincil oluşturucu parametrelerinin değerini depolamak için bir alan oluşturmasını gerektirmediğini anlamak önemlidir. İkinci örnek, bir yöntemin içinde birincil oluşturucu parametresini kullandı ve bu nedenle derleyicinin onlar için depolama alanı oluşturmasını gerekti. Derleyici, birincil oluşturucular için yalnızca bu 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 ekleme

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ın birincil oluşturucusunu çağırabilirsiniz. Temel sınıfta birincil oluşturucuyu çağırması gereken türetilmiş bir sınıf yazmanın en kolay yolu budur. Örneğin, banka olarak farklı hesap türlerini temsil eden sınıf hiyerarşisini göz önünde bulundurun. Temel sınıf aşağıdaki koda benzer olacaktır:

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ının ve bir sahibin özelliklerine sahiptir. Tamamlanan uygulamada, temel sınıfa diğer ortak işlevler eklenir.

Birçok tür oluşturucu parametrelerinde daha belirgin doğrulama gerektirir. Örneğin, BankAccount ve parametreleri için owner belirli gereksinimleri vardır: owner değeri veya boşluk olmamalıdır null ve accountID 10 basamak içeren accountID 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));
}

Önceki örnekte, oluşturucu parametrelerini özelliklere atamadan önce nasıl doğrulayabileceğiniz gösterilmektedir. gibi yerleşik yöntemleri veya gibi String.IsNullOrWhiteSpace(String)ValidAccountNumberkendi doğrulama yönteminizi kullanabilirsiniz. Önceki ö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ı sunabilir:

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 dizimini kullanarak 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ın birincil oluşturucusunu ç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: Hem türetilmiş hem de temel sınıflarda kullanıldığı için birincil oluşturucu parametresinin birden çok kopyasını oluşturmak mümkündür. Aşağıdaki kod örneği, ve accountID alanının her biri owner için 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}";
}

Vurgulanan satır, yönteminin ToString temel sınıf özellikleri ( ve accountID) yerine birincil oluşturucu parametrelerini (ownerOwner ve AccountID) kullandığını gösterir. Sonuç, türetilmiş sınıfın SavingsAccount bu kopyalar için depolama alanı oluşturmasıdır. 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üretilen sınıfın örneği bu 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.

Özet

Birincil oluşturucuları tasarımınıza en uygun şekilde kullanabilirsiniz. Sınıflar ve yapılar için birincil oluşturucu parametreleri, çağrılması gereken bir oluşturucuya yönelik parametrelerdir. Özellikleri başlatmak için bunları kullanabilirsiniz. Alanları başlatabilirsiniz. Bu özellikler veya alanlar sabit veya değişebilir olabilir. Bunları yöntemlerde kullanabilirsiniz. Bunlar parametredir ve bunları tasarımınıza en uygun şekilde kullanırsınız. Örnek oluşturucuları ve önerilen birincil oluşturucu belirtimi hakkında C# programlama kılavuzu makalesinde birincil oluşturucular hakkında daha fazla bilgi edinebilirsiniz.