مقدمه

هر شیئی که در یک برنامه ایجاد می‌شود، یک "طول عمر" (Lifetime) مشخص دارد. این طول عمر از لحظه‌ی تخصیص حافظه برای شیء شروع شده و تا زمانی که آن حافظه توسط سیستم بازپس گرفته می‌شود، ادامه دارد. درک فرآیند طول عمر اشیاء، به خصوص برای انواع ارجاعی که در حافظه‌ی Heap ذخیره می‌شوند، برای نوشتن برنامه‌های بهینه و جلوگیری از مشکلات مربوط به حافظه، بسیار حیاتی است. در .NET، این فرآیند به صورت خودکار توسط یک مکانیزم هوشمند به نام زباله‌روب (Garbage Collector - GC) مدیریت می‌شود. در این درس، سه مرحله‌ی اصلی طول عمر یک شیء را بررسی می‌کنیم: ایجاد، استفاده و تخریب.

مرحله اول: ایجاد و ساخت (Creation and Construction)

طول عمر یک شیء با فراخوانی کلمه‌ی کلیدی new آغاز می‌شود. وقتی شما کدی مانند new Person() را اجرا می‌کنید، Common Language Runtime (CLR) دو کار اصلی انجام می‌دهد:

  1. تخصیص حافظه: یک بلوک حافظه‌ی خالی و به اندازه‌ی کافی بزرگ برای نگهداری تمام فیلدهای نمونه‌ی آن کلاس، در ناحیه‌ای از حافظه به نام Heap مدیریت‌شده (Managed Heap) پیدا و رزرو می‌کند.
  2. فراخوانی سازنده: سازنده‌ی (constructor) مناسب کلاس را بر روی این بلوک حافظه‌ی جدید فراخوانی می‌کند تا فیلدهای آن را مقداردهی اولیه کند و شیء را در یک وضعیت معتبر قرار دهد.
Copy Icon Program.cs
public class Person
{
    public Person()
    {
        Console.WriteLine("A new Person object is being constructed.");
    }
}

// --- Usage ---
// 1. Memory is allocated on the heap.
// 2. The constructor is called.
Person p1 = new Person();

پس از اجرای این کد، یک شیء Person در Heap وجود دارد و متغیر p1 (که روی Stack قرار دارد) به آن ارجاع می‌دهد. در این لحظه، شیء "زنده" است و مرحله‌ی بعدی عمر خود را آغاز می‌کند.

مرحله دوم: در حال استفاده (In Use)

تا زمانی که یک شیء "قابل دسترس" باشد، زنده در نظر گرفته می‌شود و حافظه‌ی آن توسط GC دست‌نخورده باقی می‌ماند. یک شیء قابل دسترس (reachable) است اگر حداقل یک ارجاع فعال به آن وجود داشته باشد. این ارجاع می‌تواند:

  • یک متغیر محلی در یک متد در حال اجرا باشد (مانند p1 در مثال بالا).
  • یک فیلد استاتیک باشد.
  • یک فیلد در یک شیء دیگر باشد که خود آن شیء قابل دسترس است.

CLR یک گراف از تمام اشیاء قابل دسترس را نگهداری می‌کند. این گراف از نقاطی به نام "ریشه‌های برنامه" (application roots) که شامل متغیرهای استاتیک و متغیرهای محلی روی پشته‌ی فراخوانی (call stack) هستند، شروع می‌شود.

مرحله سوم: تخریب و نقش زباله‌روب (Garbage Collector)

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

  • متغیری که به آن ارجاع می‌دهد، از محدوده‌ی خود خارج شود (مثلاً متد به پایان برسد).
  • مقدار null به متغیر ارجاع‌دهنده اختصاص داده شود.
  • متغیر ارجاع‌دهنده به شیء دیگری ارجاع داده شود.
Copy Icon Program.cs
void CreateObjects()
{
    Person p2 = new Person();
    Person p3 = new Person();
    p3 = p2; // The original object p3 pointed to is now unreachable.
} // When the method ends, p2 and p3 go out of scope.
  // Now the object that p2 was pointing to also becomes unreachable.

شیء غیرقابل دسترس، بلافاصله از حافظه پاک نمی‌شود. در عوض، به عنوان "زباله" علامت‌گذاری شده و کاندیدای حذف توسط زباله‌روب (GC) می‌شود. GC یک فرآیند خودکار و بسیار بهینه در .NET است که به صورت دوره‌ای اجرا شده، اشیاء غیرقابل دسترس را پیدا کرده و حافظه‌ی آن‌ها را آزاد می‌کند تا برای اشیاء جدید قابل استفاده باشد.

زباله‌روب چگونه کار می‌کند؟ (نگاهی ساده)

الگوریتم اصلی GC بر پایه‌ی فرآیند "علامت‌گذاری و پاکسازی" (Mark and Sweep) است:

  1. فاز علامت‌گذاری (Mark Phase): GC از ریشه‌های برنامه شروع کرده و تمام گراف اشیاء را پیمایش می‌کند. هر شیئی که در این پیمایش دیده شود، به عنوان "زنده" یا "قابل دسترس" علامت‌گذاری می‌شود.
  2. فاز پاکسازی (Sweep Phase): GC تمام حافظه‌ی Heap را مرور کرده و هر شیئی را که در فاز قبل علامت‌گذاری نشده باشد، به عنوان زباله شناسایی کرده و حافظه‌ی آن را آزاد می‌کند.

این فرآیند به صورت خودکار و در زمان‌هایی که سیستم فشار حافظه‌ی کمی را تجربه می‌کند، انجام می‌شود تا تأثیر آن بر عملکرد برنامه به حداقل برسد. این مدیریت خودکار حافظه یکی از بزرگ‌ترین مزایای برنامه‌نویسی در محیط‌های مدیریت‌شده مانند .NET است.

یک نکته در مورد Finalizers (تخریب‌گرها)

C# مفهومی به نام تخریب‌گر یا Finalizer (که با سینتکسی شبیه به سازنده اما با یک `~` در ابتدا تعریف می‌شود) دارد. این متد ویژه‌ای است که GC قبل از پاک کردن حافظه‌ی یک شیء، آن را فراخوانی می‌کند.

قانون مهم: به عنوان یک برنامه‌نویس C#، شما تقریباً هرگز نباید یک تخریب‌گر بنویسید. پیاده‌سازی آن‌ها پیچیده است، عملکرد برنامه را کاهش می‌دهد و زمان اجرای آن‌ها غیرقطعی است. تنها کاربرد صحیح آن‌ها برای آزاد کردن منابع مدیریت‌نشده است که برای این کار نیز، روش بسیار بهتر و مدرن‌تر، استفاده از اینترفیس IDisposable و بلوک using است که در درس‌های قبل به آن اشاره شد و در درس‌های آینده به تفصیل بررسی خواهد شد.