مقدمه

در درس قبل، اصل کپسوله‌سازی را با استفاده از فیلدهای خصوصی (private) و پراپرتی‌های عمومی (public) پیاده‌سازی کردیم. دیدیم که این الگو به ما اجازه می‌دهد تا با افزودن منطق در اکسسرهای get و set، کنترل کاملی بر روی داده‌های کلاس داشته باشیم. اما در بسیاری از موارد، ما به هیچ منطق سفارشی در پراپرتی نیاز نداریم و صرفاً می‌خواهیم مقدار یک فیلد خصوصی را در معرض دید قرار دهیم. در چنین سناریوهایی، نوشتن کامل فیلد پشتیبان و اکسسورها می‌تواند به کدی تکراری و طولانی منجر شود. برای حل این مشکل، C# یک سینتکس بسیار مختصر و تمیز به نام پراپرتی‌های خودکار (Auto-Implemented Properties) ارائه می‌دهد.

الگوی تکراری پراپرتی‌های کامل

بیایید ابتدا به الگویی که در درس قبل برای یک پراپرتی ساده (بدون منطق اعتبارسنجی) استفاده کردیم، نگاهی بیندازیم.

Copy Icon Program.cs
public class Student
{
    // 1. The private backing field
    private string _name;

    // 2. The full public property
    public string Name
    {
        get { return _name; }
        set { _name = value; }
    }
}

در این کد، تمام کاری که پراپرتی Name انجام می‌دهد این است که به عنوان یک واسط برای فیلد خصوصی _name عمل کند. هیچ منطق اضافه‌ای در کار نیست. این کد کاملاً صحیح است، اما می‌توان آن را بسیار کوتاه‌تر نوشت. این نوع کد تکراری که صرفاً برای پیاده‌سازی یک الگو نوشته می‌شود، اصطلاحاً کد boilerplate نامیده می‌شود.

پراپرتی‌های خودکار: راه حل مختصر

پراپرتی‌های خودکار به ما این امکان را می‌دهند که بدون نیاز به تعریف صریح یک فیلد خصوصی، یک پراپرتی عمومی تعریف کنیم. کامپایلر C# به صورت خودکار یک فیلد پشتیبان خصوصی و ناشناس در پشت صحنه ایجاد می‌کند.

سینتکس پراپرتی‌های خودکار

سینتکس این نوع پراپرتی‌ها بسیار ساده است:

Copy Icon C#
public string Name { get; set; }

همین! کد بالا دقیقاً معادل کد طولانی‌ای است که در بخش قبل نوشتیم. کامپایلر به طور خودکار فیلد پشتیبان را ایجاد کرده و اکسسورهای get و هر set را به آن متصل می‌کند. بیایید کلاس Student را با استفاده از این سینتکس بازنویسی کنیم.

Copy Icon Program.cs
public class Student
{
    // Auto-implemented property for Name
    public string Name { get; set; }

    // Auto-implemented property for StudentID
    public int StudentID { get; set; }
}

// --- Usage ---
Student student1 = new Student();
student1.Name = "Alice";
student1.StudentID = 101;

Console.WriteLine($"Student: {student1.Name}, ID: {student1.StudentID}");

از دید کدی که از این کلاس استفاده می‌کند، هیچ تفاوتی بین یک پراپرتی کامل و یک پراپرتی خودکار وجود ندارد. هر دو به یک شکل فراخوانی و استفاده می‌شوند. این ویژگی به ما اجازه می‌دهد کدهایی بسیار تمیزتر و خواناتر بنویسیم.

کنترل دسترسی در پراپرتی‌های خودکار

ممکن است فکر کنید با استفاده از پراپرتی‌های خودکار، قابلیت کنترل دسترسی را از دست می‌دهیم، اما اینطور نیست. ما همچنان می‌توانیم برای اکسسورهای get و set سطح دسترسی متفاوتی تعریف کنیم.

پراپرتی‌های فقط-خواندنی خودکار

یک سناریوی بسیار رایج، ایجاد پراپرتی‌هایی است که مقدار آن‌ها فقط در زمان ساخت شیء (در سازنده) تعیین می‌شود و پس از آن نباید از خارج کلاس تغییر کند. برای این کار، می‌توانیم اکسسور set را به صورت private تعریف کنیم.

Copy Icon Program.cs
public class Transaction
{
    // This property can be read from anywhere, 
    // but can only be set from within this class.
    public Guid TransactionId { get; private set; }
    public decimal Amount { get; set; }

    public Transaction(decimal amount)
    {
        // We can set the private setter from inside the class (e.g., in the constructor)
        TransactionId = Guid.NewGuid();
        Amount = amount;
    }
}

Transaction tx = new Transaction(50.0m);
Console.WriteLine($"ID: {tx.TransactionId}, Amount: {tx.Amount}");

// This will cause a compile-time error because the setter is private.
// tx.TransactionId = new Guid();

در این مثال، TransactionId یک شناسه‌ی یکتا برای تراکنش است که به محض ایجاد، نباید تغییر کند. با private کردن اکسسور set، ما این تضمین را ایجاد کرده‌ایم.

اکسسرهای init-only

در نسخه‌های مدرن C# (9.0 و بالاتر)، راهکار بهتری برای ایجاد پراپرتی‌های تغییرناپذیر (immutable) معرفی شده است: اکسسور init. یک پراپرتی که با init تعریف می‌شود، فقط می‌تواند در زمان ساخت شیء (در سازنده یا در یک مقدارده اولیه‌ی شیء) مقدار بگیرد. پس از آن، به یک پراپرتی فقط-خواندنی تبدیل می‌شود.

Copy Icon Program.cs
public class ImmutablePoint
{
    public int X { get; init; }
    public int Y { get; init; }
}

// We can set init-only properties during object initialization
ImmutablePoint p1 = new ImmutablePoint { X = 10, Y = 20 };

// But we cannot change them after the object is created
// This line will cause a compile-time error!
// p1.X = 100;

اکسسرهای init برای طراحی انواع داده‌ی تغییرناپذیر که امنیت و پیش‌بینی‌پذیری کد را افزایش می‌دهند، ابزاری بسیار قدرتمند و مدرن هستند.

چه زمانی از کدام نوع پراپرتی استفاده کنیم؟

  • پراپرتی خودکار ({ get; set; }): گزینه‌ی پیش‌فرض شما. زمانی که به سادگی می‌خواهید یک فیلد را در معرض دید قرار دهید، از این نوع استفاده کنید.
  • پراپرتی خودکار فقط-خواندنی ({ get; private set; } یا { get; init; }): زمانی که می‌خواهید یک مقدار پس از ایجاد شیء تغییر نکند (یا فقط از داخل کلاس تغییر کند)، این گزینه‌ها عالی هستند. init تغییرناپذیری قوی‌تری را تضمین می‌کند.
  • پراپرتی کامل (با فیلد پشتیبان): فقط زمانی از این نوع استفاده کنید که نیاز به اجرای یک منطق سفارشی در اکسسور get یا set دارید (مانند اعتبارسنجی داده‌ها، اجرای یک محاسبه، یا اطلاع‌رسانی در مورد تغییر مقدار).