مقدمه

کپسوله‌سازی (Encapsulation) یکی از ستون‌های اصلی برنامه‌نویسی شیءگرا (OOP) است. این اصل به معنای بسته‌بندی داده‌ها (فیلدها) و متدهایی که روی آن داده‌ها کار می‌کنند، در یک واحد به نام کلاس است. اما بخش مهم‌تر کپسوله‌سازی، مخفی‌سازی داده‌ها (Data Hiding) است؛ یعنی محدود کردن دسترسی مستقیم به وضعیت داخلی یک شیء از دنیای خارج. این کار به ما اجازه می‌دهد تا از یکپارچگی داده‌ها محافظت کرده و تضمین کنیم که شیء همیشه در یک وضعیت معتبر باقی می‌ماند. در این درس، با ابزارهایی که C# برای پیاده‌سازی این اصل قدرتمند در اختیار ما قرار می‌دهد، یعنی سطوح دسترسی و پراپرتی‌ها، آشنا می‌شویم.

مشکل فیلدهای عمومی (Public Fields)

در درس‌های قبل، برای سادگی، فیلدهای کلاس‌هایمان را به صورت public تعریف کردیم. این کار به کدهای خارج از کلاس اجازه می‌دهد تا مقادیر این فیلدها را مستقیماً بخوانند و تغییر دهند. هرچند این روش ساده به نظر می‌رسد، اما یک مشکل اساسی دارد: کلاس هیچ کنترلی بر روی مقادیری که به فیلدهایش اختصاص داده می‌شود، ندارد.

Copy Icon Program.cs
public class Employee
{
    public string Name;
    public int Age;
}

// --- In another part of the program ---
Employee emp = new Employee();
emp.Name = "John";
emp.Age = -50; // Invalid state! Age cannot be negative.

Console.WriteLine($"{emp.Name} is {emp.Age} years old.");

در مثال بالا، کد خارج از کلاس توانست یک مقدار نامعتبر (-50) را به فیلد Age اختصاص دهد. این کار یکپارچگی شیء emp را از بین می‌برد. کلاس Employee هیچ راهی برای جلوگیری از این اتفاق ندارد. اینجاست که کپسوله‌سازی با مخفی کردن فیلدها و فراهم کردن یک راه کنترل‌شده برای دسترسی به آن‌ها، به ما کمک می‌کند.

سطوح دسترسی (Access Modifiers)

اولین قدم برای کپسوله‌سازی، استفاده از سطوح دسترسی است. این کلمات کلیدی مشخص می‌کنند که یک عضو (فیلد، متد، پراپرتی و ...) از کجا قابل مشاهده و استفاده است. رایج‌ترین سطوح دسترسی عبارتند از:

  • public: عضو از هر کدی، چه در داخل و چه در خارج از اسمبلی (پروژه)، قابل دسترسی است.
  • private: عضو فقط از داخل همان کلاسی که در آن تعریف شده، قابل دسترسی است. این سطح دسترسی، پیش‌فرض اعضای کلاس است.

یک اصل مهم در طراحی شیءگرا این است: فیلدها همیشه باید private باشند. با خصوصی کردن فیلدها، ما دسترسی مستقیم از خارج را مسدود می‌کنیم و کنترل کامل داده‌ها را در دست کلاس قرار می‌دهیم.

Copy Icon Program.cs
public class Employee
{
    private string _name;
    private int _age; // Now the field is private
}

// --- In another part of the program ---
Employee emp = new Employee();
// This will now cause a compile-time error!
// Error: 'Employee._age' is inaccessible due to its protection level.
emp._age = 30; 

در این کد، با private کردن فیلد _age (استفاده از `_` در ابتدای نام فیلدهای خصوصی یک قرارداد رایج است)، دیگر نمی‌توان از خارج کلاس به آن دسترسی داشت. حالا سؤال این است: چگونه مقادیر را بخوانیم یا تغییر دهیم؟ پاسخ در پراپرتی‌ها نهفته است.

پیاده‌سازی کپسوله‌سازی با پراپرتی‌ها

پراپرتی‌ها (Properties) اعضای ویژه‌ای هستند که یک راه انعطاف‌پذیر برای خواندن، نوشتن یا محاسبه‌ی مقدار یک فیلد خصوصی فراهم می‌کنند. از دید کدی که از کلاس استفاده می‌کند، پراپرتی‌ها شبیه فیلدهای عمومی به نظر می‌رسند، اما در داخل کلاس، آن‌ها مانند متدهایی به نام اکسسور (accessor) پیاده‌سازی می‌شوند.

اکسسرهای get و set

یک پراپرتی می‌تواند دارای دو اکسسور باشد:

  • اکسسور get: زمانی اجرا می‌شود که مقدار پراپرتی خوانده می‌شود. این اکسسور باید مقداری از نوع پراپرتی را برگرداند.
  • اکسسور set: زمانی اجرا می‌شود که یک مقدار جدید به پراپرتی اختصاص داده می‌شود. این اکسسور به یک پارامتر ضمنی به نام value دسترسی دارد که حاوی مقدار جدید است.

بیایید کلاس Employee را با استفاده از یک پراپرتی برای فیلد _age بازنویسی کنیم و در اکسسور set، منطق اعتبارسنجی را اضافه کنیم.

Copy Icon Program.cs
public class Employee
{
    private int _age;

    // Public property to control access to the private _age field
    public int Age 
    {
        get 
        { 
            return _age; 
        }
        set 
        {
            // Validation logic inside the setter
            if (value > 0 && value < 120)
            {
                _age = value;
            }
            else
            {
                Console.WriteLine("Invalid age specified!");
            }
        }
    }
}

// --- Usage ---
Employee emp = new Employee();
emp.Age = 35; // The 'set' accessor is called. 'value' is 35.
Console.WriteLine($"Employee's age: {emp.Age}"); // The 'get' accessor is called.

emp.Age = -50; // The 'set' accessor is called. The condition is false.
Console.WriteLine($"Employee's age is still: {emp.Age}");

اکنون کلاس Employee کنترل کاملی بر روی فیلد _age دارد. هرگاه کدی تلاش کند مقدار Age را تنظیم کند، اکسسور set اجرا شده و مقدار ورودی (value) را اعتبارسنجی می‌کند. اگر مقدار معتبر باشد، به فیلد خصوصی _age اختصاص داده می‌شود؛ در غیر این صورت، یک پیام خطا نمایش داده شده و مقدار فیلد بدون تغییر باقی می‌ماند. این قدرت واقعی کپسوله‌سازی است.

پراپرتی‌های فقط-خواندنی و محاسباتی

شما می‌توانید با حذف اکسسور set، یک پراپرتی فقط-خواندنی (Read-Only) ایجاد کنید. این کار برای مقادیری که نباید از خارج کلاس تغییر کنند یا برای مقادیر محاسباتی بسیار مفید است.

Copy Icon Program.cs
public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    
    // A read-only, computed property. It has no 'set' accessor.
    public string FullName
    {
        get 
        {
            return $"{FirstName} {LastName}";
        }
    }
}

Person p = new Person();
p.FirstName = "Sara";
p.LastName = "Jones";

Console.WriteLine(p.FullName); // Output: Sara Jones
// p.FullName = "Anything"; // This would cause a compile-time error.

در این مثال، پراپرتی FullName یک فیلد پشتیبان ندارد. در عوض، مقدار آن هر بار که خوانده می‌شود، با ترکیب FirstName و LastName محاسبه می‌شود. چون اکسسور set برای آن تعریف نشده، این پراپرتی فقط-خواندنی است. در درس بعدی با روش خلاصه‌تری برای تعریف پراپرتی‌ها آشنا خواهیم شد.