تو CLR نیازه تا تمامی انواع در نهایت از کلاس مخصوصی به نام System.Object مشتق بشن. اینکار به دلایل مختلفی انجام میشه و بهترین دلیلی که میشه برای اون آورد همونیه که اریک لیپرت (Eric Lippert) یکی از اعضای سابق تیم طراحی زبان برنامه نویسی #C اینجا آورده: "... در مقایسه با روشهای دیگه، اینکار بیشترین ارزش رو برای برنامه نویسا فراهم میکنه ...". (اطلاعات خیلی بیشتر درباره نحوه کارکرد CLR در رابطه با اشیا)
البته باید در نظر داشت که تو محیط دات نت این ارثبری به صورت ضمنی برای تمامی انواع پایه اعمال میشه. این جمله یعنی اینکه دو تعریف زیر از نظر CLR کاملا یکسانن:
// Implicitly derived from Object class Employee { ... } // Explicitly derived from Object class Employee : System.Object { ... }
بنابراین تمامی انواع تو محیط دات نت، شامل یکسری اعضای پایه هستن که تو کلاس Object تعریف شدن. در ادامه با این اعضا بیشتر آشنا میشیم.
درضمن کلاس Object هم مثل بسیاری از کلاسهای پایه ای دیگه دات نتی تو اسمبلی mscorlib.dll تعریف و پیاده سازی شده.
اعضای public این کلاس عبارتنداز:
1. متد Equals: همونطور که از نام این متد برمیاد، کارش مقایسه برابری دو شی هست. پیاده سازی این متد هم بصورت زیره:
public virtual bool Equals(Object obj) { return RuntimeHelpers.Equals(this, obj); }
کلاس RuntimeHelpers یه کلاس خاصه که تو فضای نام System.Runtime.CompilerServices پیاده شده. پیاده سازی بیشتر متدهای موجود تو این کلاس توسط کدهای مدیریت نشده و داخلی CLR انجام شده. مثلا پیاده سازی متد Equals تو این کلاس بصورت زیره:
[System.Security.SecuritySafeCritical] [ResourceExposure(ResourceScope.None)] [MethodImplAttribute(MethodImplOptions.InternalCall)] public new static extern bool Equals(Object o1, Object o2);نکته این پیاده سازی استفاده از خاصیت MethodImplAttribute هست که مشخص کرده پیاده سازی این متد بصورت InternalCall یا درون کدهای native خود CLR انجام شده. (اطلاعات بیشتر)
نحوه استفاده از این متد هم بصورت زیره:
var a = new object(); var b = new object(); var equals = a.Equals(b);
این متد درصورتیکه هر دو نمونه درحال مقایسه، مقداری برابر داشته باشن، true برمیگردونه و در غیر اینصورت false برگشت میده. البته این برابری مبحثی نسبتا پیچیده و مفصله که توضیحش نیاز به بررسی بیشتر انواع تو دات نت داره که در مباحث بعدی ارائه میشه. برای کسب اطلاعات بیشتر هم میشه به اینجا رجوع کرد.
کلاس Object یه متد استاتیک به همین نام داره که دو پارامتر ورودی دریافت میکنه. تو پیاده سازی این متد استاتیک در نهایت از متد Equals پارامتر اول استفاده میشه. پیاده سازی این متد یه چیزی شبیه به کد زیره:
public static bool Equals(Object objA, Object objB) { if (objA == objB) return true; if (objA == null || objB == null) return false; else return objA.Equals(objB); }
یه متد استاتیک دیگه هم به نام ReferenceEquals تو کلاس Object وجود داره که صرفا با استفاده از اپراتور == برابری ریفرنس دو شی رو مشخص میکنه. پیاده سازی این متد هم بصورت زیره:
public static bool ReferenceEquals (Object objA, Object objB) { return objA == objB; }
2. متد GetHashCode: همونطور که از نام این متد هم برمیاد، از اون برای تولید هش کد شی جاری استفاده میشه. درواقع تو این متد پیاده سازی پیش فرض و اولیه تولید هش کد یه شی وجود داره. پیاده سازی جاری این متد بصورت زیره:
public virtual int GetHashCode() { return RuntimeHelpers.GetHashCode(this); }
متد مربوطه تو کلاس RuntimeHelpers هم بصورت زیر تعریف شده:
[System.Security.SecuritySafeCritical] [ResourceExposure(ResourceScope.None)] [MethodImplAttribute(MethodImplOptions.InternalCall)] public static extern int GetHashCode(Object o);
معلومه که مثل متد Equals پیاده سازی اصلی تو کدهای مدیریت نشده دات نت قرار داره. برای مثال کد زیر رو درنظر بگیرین:
Console.WriteLine("new object hash code: \t\t{0}", new object().GetHashCode()); Console.WriteLine("another new object hash code: \t{0}", new object().GetHashCode()); Console.WriteLine("".PadLeft(50, '=')); var a = new object(); var b = a; Console.WriteLine("a's hash code: {0}", a.GetHashCode()); Console.WriteLine("b's hash code: {0}", b.GetHashCode());
نتیجه اجرای این قطعه کد تو تصویر زیر نشون داده شده:
البته نتیجه های مختلفی با هر بار اجرا تولید میشه.
به متدهایی که کدهای هش تولید میکنند اصطلاحا Hash Function میگن. هش کد یه مقدار عددیه که از اون برای ذخیره و نگهداری اشیا تو مجموعه های hash-based (مثل کلاس Hashtable یا Dictionary) استفاده میشه. درواقع این هش کد به عنوان کلید شی تو این مجموعه ها بکار میره. بنابراین مقدار عددی هش کد برای اشیای مختلف اهمیت زیادی تو نگهداری و مدیریت داده ها تو مجموعه های برپایه هش داره.
بنابراین درصورتیکه نیاز باشه تا نوع جدیدی که تعریف میشه تو مجموعه های hash-based استفاده بشه، باید متد GetHashCode توش override بشه و یه پیاده سازی مناسب که مقدار مناسب و منحصربه فردی برای هر نمونه ایجاد میکنه، بکار بره.
درضمن متدهای GetHashCode و Equals از لحاظ مفهومی ارتباط تنگاتنگی هم با هم دارن. این ارتباط تو تعریفی خاص از برابری اشیا ارائه میشه:
دو شی ای که با هم برابرن، مقدار هش کد یکسانی هم دارن، اما عکس این مطلب لزوما برقرار نیست. یعنی برابری هش کد دو شی مختلف لزوما به معنی برابری مقداریشون نیست، چون تو محیط دات نت امکان داره دو شی متفاوت هش کدهای یکسانی داشته باشن. علاوه بر این پیاده سازی پیش فرض متد هش (همون GetHashCode) تولید مقداری یکتا رو برای اشیای مختلف تضمین نمیکنه. درضمن این مقدار برای یه شی خاص میتونه تو نسخه های مختلف دات نت فریمورک متفاوت باشه.
پس اگه تو یه نوع، متد Equals برای پیاده سازی مناسب override بشه، بهتره که متد GetHashCode هم برای یه پیاده سازی مناسب override بشه.
برای پیاده سازی این دو متد نکات مختلف زیادی وجود داره که مطالعه بیشتر تو این زمینه به خوانندگان واگذاری میشه: !.
درضمن پیاده سازی این دو متد با پیاده سازی اپراتورهای == و =! هم درگیر میشه: ! و !.
3. ToString: این متد یه رشته برمیگردونه که به نوعی شی جاری رو توصیف میکنه. تو پیاده سازی پیش فرض این متد (یعنی پیاده سازیش تو کلاس object) نام کامل نوع شی جاری برگشت داده میشه. این پیاده سازی بصورت زیر انجام شده:
public virtual String ToString() { return GetType().ToString(); }
برای مثال کد زیر رو درنظر بگیرین:
Console.WriteLine(new object().ToString()); Console.WriteLine(new Class1().ToString());
نتیجه اجرای این قطعه کد تو تصویر زیر نشون داده شده:
این متد اصلی ترین روش نمایش رشته ای اشیا تو دات نته و هر نوع جدیدی که تولید میشه درصورت نیاز به نمایش باید این متد رو برای پیاده سازی مناسب override کنه. مثلا انواع پایه ای دات نت مثل bool و int این متد رو برای نمایش درست مقدار و وضعیتشون override و پیاده سازی کردن. برای نمونه نوع System.Boolean این متد رو بصورت زیر پیاده کرده:
/*===================================ToString=================================== **Args: None **Returns: "True" or "False" depending on the state of the boolean. **Exceptions: None. ==============================================================================*/ // Converts the boolean value of this instance to a String. public override String ToString() { if (false == m_value) { return FalseLiteral; } return TrueLiteral; }
البته پیاده سازی درست متد ToString نکات زیادی داره که مطالعه بیشتر به خوانندگان واگذاری میشه: ! و !.
یکی از پرکاربردترین موارد استفاده از این متد در دیباگ کدهاست. درصورت پیاده سازی مناسب، با استفاده از این متد میشه وضعیت جاری اشیای مختلف رو تو قسمتهای مختلف برنامه مشاهده کرد. درضمن ویژوال استودیو هم بصورت خودکار از این متد برای نمایش وضعیت جاری اشیای مختلف یه برنامه در حالت دیباگ استفاده میکنه. پس اهمیت این متد برای دیباگ کدها غیرقابل انکاره.
4. GetType: این متد نوع شی جاری رو برمیگردونه. این مقدار برگشتی نمونه ای از کلاس Type هست. پیاده سازی این متد تو کلاس Object بصورت زیره:
[System.Security.SecuritySafeCritical] [Pure] [ResourceExposure(ResourceScope.None)] [MethodImplAttribute(MethodImplOptions.InternalCall)] public extern Type GetType();
همونطور که معلومه پیاده سازی این متد هم بصورت داخلی تو کدهای unmanaged انجام شده. نکته ای که اینجا باید بهش دقت کرد اینه که این متد بصورت non-virtual تعریف شده و بنابراین قابل override شدن نیست. پیاده سازی پیشفرض این متد در تمام انواع بدرستی کار میکنه و نمونه درستی از کلاس Type رو برمیگردونه.
علاوه بر 4 متد public که در بالا شرح داده شدن، کلاس Object دو متد protected هم داره:
1. MemberwiseClone: این متد یه کپی کم عمق یا shallow copy از شی جاری برمیگردونه. پیاده سازی این متد تو کلاس Object بصورت زیره:
[System.Security.SecuritySafeCritical] [ResourceExposure(ResourceScope.None)] [MethodImplAttribute(MethodImplOptions.InternalCall)] protected extern Object MemberwiseClone();
پیاده سازی اصلی این متد هم تو کدهای مدیریت نشده CLR قرار داره. این متد هم مثل متد GetType بصورت non-virtual تعریف شده، بنابراین نمیشه اونو override کرد. کار این متد ایجاد یه نمونه جدید از شی جاری و مقداردهی تمامی فیلدهای نمونه جدید با مقادیر متناظر تو شی جاریه. اما نکته ای که این عملیات رو shallow میکنه اینه که این فرایند برای خود فیلدهای شی جاری تکرار نمیشه و بنابراین برای فیلدهای reference type عملیات کپی بصورت ریفرنسی انجام میشه. در مقابل این نوع کپی، نوع دیگه ای به نام deep copy هم وجود داره و همونطور که از اسمش پیداست عملیات کپی رو بصورت عمیق انجام میده و فیلدهای ریفرنسی رو هم بصورت درست و با مقدار کپی میکنه. اطلاعات بیشتر تو این زمینه: ! و !.
2. Finalize: این متد به شی اجازه آزادسازی منابع درگیر رو قبل از اینکه توسط GC کاملا از حافظه پاک بشه، میده. پیاده سازی این متد تو کلاس Object رو نمیشه براحتی مشاهده کرد. هرچند بصورت پیش فرض هیچ پیاده سازی خاصی هم تو این متد وجود نداره. نکته اینجاس که کامپایلر #C اجازه استفاده مستقیم از این متد رو نمیده. یعنی با اینکه این متد بصورت virtual تعریف شده اما تو کد سی شارپ در دسترس نیست و نمیشه اونو بصورت مستقیم override کرد!
برای استفاده از این متد، روش دیگه ای تو برنامه های #C وجود داره. برای اینکار مفهومی به نام destructor تو کلاسهای تعریفی تو زبان #C وجود داره. این عضو شبیه به constructor یه کلاس تعریف میشه. با استفاده از این عضو میشه متد Finalize رو به نوعی override کرد. درواقع کامپایلر #C کدهای موجود تو این عضو رو در زمان کامپایل درون یه متد override شده Finalize قرار میده.
البته فرایند Finalization و پاکسازی اشیا از حافظه و عملیات درگیر تو GC پیچیده تر از این چند خط توضیحاته و مطالعه بیشتر به خوانندگان واگذار میشه.
اما برای دیدن پیاده سازی این متد تو کلاس Object میشه از ابزار ildasm استفاده کرد. پس از باز کردن این برنامه با استفاده از خط فرمان ویژوال استودیو، برای مشاهده کدهای IL کلاس object، فایل mscorlib.dll که این کلاس و بقیه انواع پایه ای دات توشه رو باید باز کرد:
مشاهده میشه که متد Finalize برای کلاس Object تعریف شده. با دابل کلیک روی اون محتوای IL این متد تو یه پنجره جدید نمایش داده میشه که بصورت زیره:
.method family hidebysig newslot virtual void Finalize() cil managed { .custom instance void System.Runtime.ConstrainedExecution.ReliabilityContractAttribute::.ctor(valuetype System.Runtime.ConstrainedExecution.Consistency,valuetype System.Runtime.ConstrainedExecution.Cer) = ( 01 00 03 00 00 00 02 00 00 00 00 00 ) .custom instance void __DynamicallyInvokableAttribute::.ctor() = ( 01 00 00 00 ) // Code size 1 (0x1) .maxstack 8 IL_0000: ret } // end of method Object::Finalize
کاملا واضحه که تو این پیاده سازی پیش فرض کد خاصی وجود نداره و دلیلی هم نداره که وجود داشته باشه، چون چیزی برای آزادسازی تو کلاس Object وجود نداره.
نمونه سازی اشیا
تو محیط CLR تمامی اشیا باید با استفاده از کلمه کلیدی new نمونه سازی بشن. عملیاتی که این اپراتور انجام میده در عمل بصورت زیر خلاصه میشه:
1. ابتدا میزان حافظه موردنیاز برای نمونه سازی شی موردنظر محاسبه میشه. برای اینکار حافظه اشغال شده توسط فیلدهای instance نوع مربوطه و تمامی انواع پایه اون تا خود کلاس Object درنظر گرفته میشه. علاوه بر این تمامی نمونه هایی که باید تو قسمت heap حافظه مدیریت شده ذخیره بشن، به دو عضو اضافه دیگه هم نیاز دارن: type object pointer و sync block index ( ! و ! ). CLR از این دو عضو اضافی برای مدیریت اشیا استفاده میکنه. میزان حافظه موردنیاز برای این دو عضو هم به مقدار محاسبه شده برای حافظه موردنیاز نمونه موردنظر اضافه میشه.
2. سپس حافظه موردنیاز برای نگهداری نمونه موردنظر تو حافظه مدیریت شده توسط CLR اختصاص داده میشه. در ادامه تمامی بایت های این حافظه اختصاصی به صفر (0) مقداردهی اولیه میشن.
3. در گام بعدی دو عضو مخصوص type object pointer و sync block index توسط CLR به مقادیر مناسبشون مقداردهی اولیه میشن.
4. سرانجام در گام پایانی کانستراکتور موردنظر با استفاده از پارامترهای فراهم شده فراخونی میشه. اینکار بصورت زنجیری باعث فراخونی کانستراکتورهای کلاسهای پایه تا کلاس Object میشه. درنهایت هم نمونه تولیدشده برگشت داده میشه.
.: . :.