نوشته شده توسط
marzban
با سلام
من تقریبا کم و بیش با مفاهیم مخرب ها و سازنده ها آشنای اندکی در حد مفاهیم دارم
می خوام که به یه نحوی ازشون در نوشتن کلاسهای مربوط به بانکهای اطلاعاتی اسفاده کنم . ولی در این مورد جیز زیادی به ذهن خودم نمی رسه آیا استفاده از سازنده و مخرب می تونه تو نوشتن این سبک برنامه نویسی کمک کنه.
لطفا راهنمایی کنید
با تشکر مرزبان
سلام.
وقتی شما Object ای رو new می کنید، باعث میشید تا ctor اون Object فراخوانی بشه. اما در مورد destructor ها وقتی اون object رو null میذارید، destructor فراخوانی نمیشه. در واقع شما کنترلی روی زمان فراخوانی اون ندارید. destructor وقتی فراخوانی میشه که GC صلاح بدونه. اما GC چی هستش؟ Garbage Collector یا همون GC، مدیریت تخصیص و آزادسازی حافظه یه برنامه رو در .NET به عهده داره. همه چیز در heap مدیریت شده رخ میده، heap ای که بسیار شبیه Heap کدهای native هستش با این تفاوت که Object ها توسط برنامه نویس آزاد نمیشن، بلکه هنگامی آزاد میشن که دیگه نیازی بهشون نیست. (بد نیست بدونید که GC در درون خودش حاوی pointer به object هایی هستش که تو heap مدیریت شده نگه داشته میشه. به این pointer ها root میگن. وقتی برنامه شروع به کار میکنه یا هر وقت شما Object ای رو new می کنید، CLR یه graph از root ها رو میگیره و تو یه زمان از پیش تعیین شده شروع به آزاد سازی همه اون object هایی میکنه که root اونها null شده. هر وقت شما یه object ای رو که new کردین، مساوی null بذارین، سیستم تشخصی میده و root مربوط به اون object رو هم null میکنه تا بعدا بتونه سر فرصت اونهایی که null هستن رو، release کنه).
اما سیستم از کجا می خواد بفهمه که دیگه نیازی به یه Object نیست؟ برای فهمیدن جواب این سوال ایتدا باید بدونیم که C# دو نوع عمومی از type ها رو پشتیبانی میکنه: value type ها و Reference Type ها.
value-type ها تایپهای enum، built-in ها و struct ها هستن. اونها میتونند مستقیم مقادیر رو در خودشون حفظ کنن. Stack، حافظه ای هستش که این نوع تایپها توسط سیستم در اون نگهداری میشن. در مقابل reference-type ها رو داریم، که در واقع reference یا pointer به یک instance رو در stack نگه میدارن و خود instance رو در heap مدیریت شده. البته من قبلا در مورد value-type ها و reference ها در این پست توضیحاتی داده بودم که خوندنش خالی از لطف نیست.
اما چه اتفاقی می افته وقتی شما یه Object ای رو new می کنید؟ CLR فرض میکنه که حافظه سیستم نامحدوده و شروع به تخصیص حافظه بصورت ترتیبی می کنه. یعنی خونه به خونه حافظه رو میره جلو و اختصاص میده به Object هایی که شما new می کنید. اما واقعیت اینه که سیستم، حافظه محدودی داره و تا ابد نمیتونه این مساله ادامه پیدا کنه، پس یه نفر باید فضاهای بلا استفاده تو heap رو آزاد کنه و اینجاست که GC توسط root هایی که بالا توضیح دادم، اقدام به آزادسازی حافظه می کنه. اما هنوز یه کار دیگه هم باید انجام بشه، و اون Compact کردن حافظه هستش. اما همه این مراحل به نظرتون باندازه کافی سریع هستش؟ فکرش رو کنید، وقتی GC شروع به آزاد سازی حافظه کنه، باید بره لیست همه root هایی که مساوی null هستن رو بیاره و دونه به دونه اقدام به آزادسازی اونا کنه که کاملا مشخصه که بسیار روند کندی هستش.
پس اومدن گفتن جای اینکه تو یه فاز بریم و object هایی که root اونها الان null هست رو آزاد کنیم، بیاییم این کار رو تو چند مرحله انجام بدیم. اما با توجه به این مفروضات که اولا Compact کردن بخشی از heap سریعتر از compact کردن کل heap هستش و ثانیا Object های قدیمیتر احتمالا بیشتر باقی می مونن تا object های جدیدتر.
اما فازها: گفتن ما 3 تا فاز تعریف میکنیم. فاز اول وقتی هستش که یه object ای new میشه. همه object هایی که new میشن تو فاز 1 ذخیره میشن تا وقتی که فاز یک پر بشه. هر وقت این اتفاق افتادش، GC شروع به آزادسازی تمام Object هایی میکنه که دیگه جایی reference نشدن و مابقی که هنوز در حال استفاده هستن compact میشن و به سمت چپ میان. (حافظه رو خونه های چپ به راست افقی در نظر بگیرید). تمام Object هایی که باقی می مونن (یعنی هنوز در حال استفاده هستن)، میرن تو فاز 2. وقتیکه دوباره فاز 1 پر شدش، همه Object هایی که تو فاز 2 بودن میرن تو فاز 3. هر وقت هر 3 فاز پر بشن، OutOfMemoryException، داده میشه.
برای اینکه کاملا متوجه نحوه عملکرد .NET Framework ها بشید، مثال زیر رو در نظر بگیرید:
public class MyClass
{
public MyClass()
{
}
~MyClass()
{
}
}
MyClass myClass = new MyClass();
myClass = null;
GC.Collect();
GC.WaitForPendingFinalizers();
به این ترتیب وقتی کلاس myClass رو new میکنیم، باعث میشیم تا ctor فراخوانی بشه. بعد تو خط دوم myClass رو null میکنم، تا root معادل اون در managed-heap هم null بشه. بعد به GC میگم شروع به کار کنه و object های reference نشده رو آزاد کنه. (از جمله myClass رو). بعد به GC میگم که صبر کنه تا عملیات Collection تموم بشه. تازه تو این نقطه هستش که destructor کلاس فوق صدا زده میشه. تو حالت عادی، وقتی شما reference ای رو null میکنید، معلوم نیستش که کی destructor اش فراخوان میشه، چون هیچ اطلاعی از وضعیت فعلی حافظه مدیریت شده نداریم و تمام تصمیم گیری ها توسط GC انجام میشه.
پس این صحیح نیستش که بخواهیم رو destructor ها تو C# برنامه ریزی کنیم. اما بعضی اوقات هستش که شما تمایل دارید تا Object ای که دارید، عملیات مشخصی رو هنگام تخریب انجام بده. به این منظور IDisposable در .NET تعریف شد. هر وقت شما نیاز داشته باشید تا عملیات خاصی رو هنگام تخریب کلاس انجام بدین، باید کلاستون رو از IDisposable درایو کنید. به این ترتیب:
public class MyClass2 : IDisposable
{
public MyClass2()
{
}
public void Dispose()
{
}
}
و هنگام استفاده
MyClass2 myClass2 = new MyClass2();
myClass2.Dispose();
یا notation بهتر اون:
using (MyClass2 myClass2 = new MyClass2())
{
}
رو میتونید استفاده کنید. حالا با در دست داشتن این اطلاعات، دوباره برمیگردیم به سوال شما... کار کردن با بانکهای اطلاعاتی از طریق ADO.NET. چون در این تکنولوژی روندی به اسم Connection Pooling پشتیبانی میشه، توصیه همگان این هستش که بعد از اتمام کار با یه Connection، اونو از بین میبرین تا resource ها به سیستم بر گردن. در واقع ADO، منابع رو برای مدت محدودی نگه میداره، چون ممکنه شما مجددا تقاضای دسترسی به یه Connection رو داشته باشین... این بهترین روش در استفاده از ADO هستش... (راستش خسته شدم از بس نوشتم، اگه خدا بخواد بقیشو بعدا می نویسم).