آشنایی با مفاهیم Finalizable و Disposable در برنامه های سی شارپ

در دوره آموزشی مقدماتی زبان سی شارپ با مفهوم سازنده ها (Constructors) آشنا شدیم و گفتیم که Constructor ها برای مقدار دهی اولیه خصوصیات و فیلد ها زمان ساخت شئ استفاده می شوند. اما مفهوم دیگری نیز وجود دارد به نام Finalize که زمان حذف شئ از داخل حافظه رخ می دهد. برای درک این مفهوم بهتر است ابتدا کمی در مورد مکانیزم مدیریت حافظه در دات نت صحبت کنیم. همانطور که مطالب قبلی گفتیم، زمانی که شما شئ ای از روی یک کلاس ایجاد می کنید، برای متغیر تعریف شده فضایی در Stack ایجاد شده، شئ در حافظه Heap یا Managed Heap که یک حافظه مدیریت شده است ایجاد می شود و آدرس حافظه Heap در Stack قرار می گیرد. حافظه Heap حافظه ای است که دائماً توسط سرویسی به نام GC یا Garbage Collector مدیریت شده و اشیاء بدون استفاده آن توسط این سرویس حذف می شوند. زمان بندی بررسی و مدیریت حافظه Heap توسط GC، توسط CLR مدیریت می شود و نمی توان زمانبندی دقیقی برای آن ارائه کرد. زمانی که شئ ای توسط GC از حافظه حذف می شود، عملیاتی با نام Finalize بر روی آن انجام می شود که اصطلاحاً عملیات پاک سازی نهایی نام دارد. اما مکانیزم این کار به چه صورت است؟ در کلاس Object که کلاس پایه تمامی Type های دات نت است، متدی وجود دارد با نام Finalize که توسط چشم غیر مسلح قابل دیدن نیست!!!! این متد توسط GC و زمان حذف شئ از حافظه Heap فراخوانی می شود. متد Finalize یک متد virtual است، یعنی می توان رفتار آن را در کلاس های فرزند مجدد تعریف کرد، اما نه با override کردن آن! برای نوشتن متد Finalize در حقیقت باید یک Destructor تعریف کنیم که دقیقاً عکس عملیات Constrcutor رفتار می کند. برای مثال، کلاسی داریم با نام Person و برای آن یک Destructor تعریف می کنیم:

public class Person
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }

    ~Person()
    {
        Console.WriteLine("Person destroyed!");
    }        
}

همانطور که مشاهده می کنید، برای نوشتن Destructor، کافیست مانند تعریف سازنده پیش فرض عمل کرده، فقط در ابتدای نام کلاس علامت ~ را قرار دهیم. زمان کامپایل شدن کد، این قطعه کد، به دستور زیر تبدیل می شود:

public override void Finalize()
{
    Console.WriteLine("Person destroyed!");
}

یعنی در حقیقت، با نوشتن Destructor، در حال override کردن متد Finalize در کلاس Object هستیم. این متد تنها در صورتی توسط GC فراخوانی می شود که در کلاس نوشته شده، Destructor نوشته شده باشد. این متد برای پاک سازی دستی حافظه استفاده می شود. برای مثال، فرض کنید داخل یک کلاس از یکسری منابع مانند فایل ها یا بانک های اطلاعاتی استفاده کرده اید و می خواهید پس از اتمام عمر شئ، عملیات پاک سازی منابع به صورت دستی انجام شود.


اینترفیس IDisposable و دستور using


در بخش قبلی با Destructor ها آشنا شدیم. شیوه دیگری برای پاک سازی دستی منابع استفاده شده توسط اشیاء وجود دارد و آن پیاده سازی اینترفیس IDisposable و استفاده از دستور using است. این اینترفیس متدی با نام Dispose دارد و بعد از پیاده سازی می توان دستورات جهت پاک سازی منابع را داخل آن نویست. برای مثال کلاس Person را به صورت Disposable تعریف می کنیم:

public class Person : IDisposable
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }

    ~Person()
    {
        Console.WriteLine("Person destroyed!");
    }

    public void Dispose()
    {
        // clean up resources here
    }
}

اما دلیل پیاده سازی اینترفیس IDisposable چیست؟ به طور حتم تمامی اینترفیس های موجود در کتابخانه دات نت وجودشان دلیل خاصی دارد. مثل اینترفیس IEnumerable که امکان پیمایش مجموعه ها توسط foreach را می دهد. اینترفیس IDisposable نیز به ما امکان استفاده از یک شئ در ساختار using را می دهد. نحوه استفاده از این دستور به شکل زیر است:

using (Person person = new Person())
{
    // write your code here
}

تنها امکان ساخت اشیاء ای در قسمت using وجود دارد که اینترفیس IDisposable را پیاده سازی کرده باشند. بعد از ورود به بدنه using، کدهای نوشته داخل بدنه using اجرا شده و به محض خروج از بدنه using، دستور Dispose که با پیاده سازی IDisposable ایجاد شده، فراخوانی می شود.

اما تفاوت مکانیزم Finalize و Dispose چیست؟ در حقیقت Finalize توسط GC فراخوانی می شود و اصطلاحاً به آن Implicit Cleanup گفته می شود، اما امکان فراخوانی متد Dispose توسط کاربر و داخل کد وجود دارد که اصطلاحاً به آن Explicit Cleanup گفته می شود.


ترکیب قابلیت های Finalize و Disposable


تا اینجا با دو مکانیزم پاک سازی منابع، یعنی Finalize و Disposable و کارآیی هر یک آشنا شدیم. بهترین روش برای پاک سازی منابع، ترکیب استفاده از متدهای Finalize و اینترفیس IDisposable است که در زیر نمونه کد پیاده سازی آن را مشاهده می کنید:

public class Person : IDisposable
{
    private bool disposed = false;

    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }

    ~Person()
    {
        Dispose(false);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                // free managed objects
            }
            // free unmanaged objects
            // other cleanups
            disposed = true;
        }
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
}

ابتدا فیلدی در کلاس تعریف کردیم با نام disposed که وضعیت کلاس که آیا عملیات Dispose بر روی آن انجام شده یا خیر را مشخص می کند. بعد یک متد virtual با نام Dispose تعریف می کنیم که این متد یک پارامتر ورودی به نام disposing را قبول می کند. این پارامتر مشخص می کند که آیا فراخوانی متد توسط بخش Dispose انجام شده یا بخش Finalize. داخل بدنه Dispose که پارامتری را به عنوان ورودی قبول می کند در صورت dispose نشده بودن شئ عملیات پاک سازی انجام می شود. در متد Dispose اصلی، ابتدا متد Dispose فراخوانی شده و بعد از آن با متد SuppressFinlize از فراخوانی متد Finalize توسط GC جلوگیری می شود. همچنین در Destructor، عملیات Dispose فراخوانی می شود.

برای پیاده سازی Destructor ها، موارد زیر را حتماً مد نظر داشته باشید:

  1. تنها در صورتی عملیات Finalize را پیاده سازی کنید که به آن حتماً نیاز است، چون این عمل بر روی Performance برنامه شما تاثیر می گذارد.
  2. در صورت نیاز به عملیات Finalize، حتماً اینترفیس IDisposable را نیز پیاده سازی کنید تا در حد امکان از فراخوانی متد Finalize جلوگیری شود.
  3. متدی که برای عملیات Finalize می نویسید حتماً به صورت protected تعریف شود نه public!
  4. در صورتی که متد حاوی پروسه Finalize در کلاس پدر نوشته شده و در کلاس های فرزند آن را override کرده اید، حتماً نسخه موجود در کلاس پدر را نیز در کلاس فرزند فراخوانی کنید.

امیدوارم که این آموزش مورد توجه دوستان قرار گرفته باشد. ITPRO باشید

نویسنده: حسین احمدی

انجمن تخصصی فناوری اطلاعات ایران

#الگوی_disposable_در_سی_شارپ #destructor_ها_در_سی_شارپ #متد_finalize_در_کلاس_object #آموزش_پیشرفته_زبان_سی_شارپ #نکات_پیشرفته_برنامه_نویسی
2 نظر
فرشید علی اکبری

سلام

آیا در صورت استفاده از Ioc Container هایی مثل StructureMap هم اینکار لزومی داره؟ چون میدونید که وقتی از این کانتینر برای یکسری از کلاسها استفاده میکنیم، پس از خروج از اون بخش/فرم، StructureMap بطور اتوماتیک اون object رو Dispose میکنه (البته در ویندوز فرم به این شکل رفتار میکنه ولی در برنامه برنامه نویسی تحت وب باید بطور دستی با یکی از فانکشن های خودش که برای اینکار درنظر گرفته شده، اونو بطور دستی dispose کنیم). نظرتون در اینمورد چیه مهندس ؟

حسین احمدی

سلام، خودتون فرمودید عملیات Dispose رو انجام میده. Dispose کردن فقط برای اشیاء ای هست که اینترفیس IDisposable رو پیاده سازی کردند. بعد موضوعی که باید بهش توجه کنید، IoC ها آگاهی نسبت به منابع استفاده شده در کلاس ها ندارن. پس باید عملیات پاک سازی حافظه رو برای کلاس ها بنویسید. IoC ها عملیات Inject کردن Dependency ها رو به بخش های مختلف برای شما انجام میدن. راهکار برای این موضوع استفاده از Nested Container ها در ابزارهای IoC مثل Structure Map یا Ninject هست. البته بهتره نگاهی هم به بحث Lifetime Manager ها در IoC ها بندازید.

نظر شما
برای ارسال نظر باید وارد شوید.
از سرتاسر توسینسو
تنظیمات حریم خصوصی
تائید صرفنظر
×

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