متدها و اعضای کلاس‌ها در C#

معرفی مفاهیم virtual, override, abstract‌ و sealed در #C :

همانطور که مستحضر هستید کلمه‌ی Polymorphism به معنای چندریختی است. در برنامه‌نویسی شیءگرا پلی مورفیسم اغلب به عنوان یک تغییردهنده رفتار یا یک واسط چند متدی مطرح می‌شود.

در زبان برنامه‌نویسی #C پلی مورفیسم به سه شکل ممکن پیاده‌سازی می‌شود:

1- استفاده از متدهای virtual و override کردن آنها در کلاس فرزند

2- بهره‌گیری از متدهای abstract در کلاس والد

3- بهره‌گیری از قابلیت واسط‌ها یا Interfaceها ( واسط‌ها در فصول بعدی به تفصیل توضیح داده خواهند شد)

متدهای virtual

فرض کنید یک کلاس والد به نام Shape دارید که در آن متدی به نام Draw تعریف شده است. متد Draw وظیفه‌ی ترسیم یک شیء یا شکل را به عهده دارد. این متد در تمام کلاس‌هایی که از این کلاس مشتق می‌شوند قابل استفاده است. بنابراین کلاس Shape را به صورت زیر می‌نویسم:

حال باید کلاس‌های فرزند مرتبط با این کلاس را تعریف کنیم بنابراین سه کلاس به نام‌های Triangle و Rectangle و Circle را تعریف می‌کنیم که هر سه از کلاس والد یعنی Shape مشتق شده اند:

هر سه کلاسی که در فوق تعریف کردیم می‌توانند از متد Draw استفاده کنند زیرا این متد در کلاس اصلی والد تعریف شده است. حال اگر شیءای بسازیم آنگاه:

در نهایت خروجی دستورات به صورت زیر است:

اما این خروجی مورد پسند نیست و باید برای هر کلاس یک شکل کشیده شود در نهایت برای رفع این مشکل متد موجود در کلاس والد را به صورت virtual تعریف کرده و آن را درون کلاس فرزند override می‌کنیم. بنابراین تغییراتی در کلاس والد داده و متد Draw را به صورت زیر تعریف می‌کنیم:

حال همین تغییرات را درون کلاس‌های فرزند انجام می‌دهیم:

حال خروجی دستورات فوق به صورت زیر اصلاح می‌شوند:

همچنین اگر از کلمه‌ی کلیدی base درون متد کلاس فرزند استفاده کنیم محتوا و متد کلاس پایه نمایش داده خواهد شد:

استفاده از روش virtual و override‌ تنها به متدها ختم نمی‌شود بلکه می‌توان برای خصوصیات یا Property ها نیز این کار را انجام داد:

معرفی کلاس‌ها و متدهای abstract و sealed

هنگامیکه یک برنامه را می‌نویسیم و کلاس‌ها و اعضای آن را مشخص می‌کنیم باید برای استفاده از کلاس‌ها یک سری محدودیت بگذاریم. مثلا یک کلاس پایه داریم که این کلاس نباید توسط سایر کلاس ها مورد استفاده قرار بگیرد و تنها اعضای آن کلاس و کلاس هایی که از آن به ارث برده‌اند، توانایی دسترسی به متدها و اعضای آن را دارند یا مثلا کلاسی را ایجاد کرده‌ایم که با اعمال محدودیت‌هایی اجاره ایجاد کلاس فرزند از آن را نمی‌دهیم. برای اعمال همچین محدودیت‌هایی از کلاس‌ها یا متدهایی با فرم abstract یا sealed استفاده می‌شود.

کلاس‌ها و اعضاء abstract

به مثال قبلی باز می‌گردیم که یک کلاس والد به نام Shape داشتیم و از روی این کلاس سه فرزند ساخته بودیم:

اگر به مثال بالا مراجعه کنید متوجه می‌شوید که کلاس Shape عملا برای ما کاربردی ندارد. به عبارت دیگر در طول برنامه‌ی اصلی از آن استفاده نشده است. یعنی باید محدودیتی اعمال کنیم که از روی کلاس Shape شیء‌ای ایجاد نشود. برای اینکار کافیست کلاس Shape را از نوع abstract تعریف کنیم. بنابراین طی یک تعریف کلی برای abstract‌ داریم:

اگر کلاسی به صورت abstract ایجاد شود، در طول برنامه نمی‌توان از روی آن شیءای ساخت.

به مثال زیر توجه کنید:

حال اگر بخواهیم از روی کلاس Shape یک شیء ایجاد کنیم با خطای زیر مواجه می‌شویم:

یعنی نمی‌توان از روی کلاس‌هایی که به صورت abstract‌ تعریف شده‌اند شیءای ایجاد کرد.

اما کاربردهای بیشتری از کلاس abstract انتظار می‌رود. در کلاس‌های abstract‌ می‌توان به طور مشابه متدهایی را تعریف کرد که به صورت abstract‌ باشند. این متدها تنها شامل signature هستند یعنی بدنه‌ای نداشته و پس از تعریف آنها به علامت ; ختم می‌شوند.  درصورتیکه یک متد به صورت abstract تعریف شود بدین گونه است که حتما باید آن را جهت استفاده در طی برنامه یا کلاس دیگر override کنند. برای مثال یکبار دیگر کلاس Shape‌ را بازنویسی می‌کنیم:

همانطور که ملاحظه می‌کنید متدی به نام Draw‌ وجود دارد که بدنه‌ای ندارد. علت این امر تعریف این متد به صورت abstract است. حال اگر کلاس فرزندی از کلاس Draw به ارث ببرد باید همواره متد درون آن به صورت abstract قرار بگیرد. بنابراین داریم:

کلاس‌ها و اعضاء sealed

در بخش وراثت آموزش دادیم که همواره می‌توان یک زنجیره‌ی وراثت در اختیار داشت مثلا کلاس B از کلاس A و کلاس C از کلاس B مشتق شود. حال درنظر بگیرید که می‌خواهیم به نحوی این زنجیره‌ی وراثت را قطع کنیم. برای اینکار باید کلاس موردنظر را از نوع sealed تعریف کنیم. بنابراین در طی یک تعریف کلی داریم:

کلاس‌هایی که به صورت sealed مورد استفاده قرار می‌گیرند، باعث حذف زنجیره وراثت می‌شوند.

به مثال زیر توجه کنید:

در این کد کلاس B‌ از نوع sealed تعریف شده است و این موضوع بدین معناست که هیچ کلاس دیگری نمی‌تواند از کلاس B مشتق شود و یا متدها و ویژگی‌هایی را به ارث ببرد. به عنوان مثال اگر کد زیر را پیاده سازی کنیم:

آنگاه با خطای زیر روبه‌رو می‌شویم:

بدین معنی‌ست که شما نمی‌توانید از یک کلاس که به صورت sealed‌ تعریف شده است ویژگی یا متدی را به ارث ببرید.

یکی دیگر از کاربردهای عبارت sealed جلوگیری از override کردن یک متد است. به مثال زیر توجه کنید:

این مثال بدین صورت عمل می‌کند که اگر کلاسی از کلاس Rectangle مشتق شد، دیگر قابلیت override کردن متد Draw را نداشته باشد زیر در متد موجود در کلاس Rectangle، متد به صورت sealed تعریف شده است.

فرق Class و Struct در #C :

Struct در سی شارپ یک جایگزین سبک حجم برای کلاس ها هستند. پس زمانی که می خواهیم نسخه های زیادی از یک داده را مقداردهی کنیم از Struct در سی شارپ استفاده می کنیم. تو این قسمت در مورد مفهوم Struct حرف بزنیم و این که Struct چه فرقی با Class دارد، برای ایجاد Struct ابتدا یک Class اضافه می کنیم

بعد از ایجاد کلاس نام کلیدی کلاس را پاک می کنیم و می توانیم نامی که برای کلاسمان در نظر گرفتیم هم پاک کنیم و نام دلخواه خودمان بگذاریم.

و مثل کلاس می توانیم فیلد و تابع داشته باشیم:

داخل پنجره فرم شده و از Struct نمونه می سازیم:

سوال : اگر Struct تمام قابلیت های Class را دارد و هر دو مساوی هستند چرا هر دو را داخل زبان سی شارپ گذاشتند؟

این دو تا یک تفاوت هایی با هم دارند که الان با هم بررسی می کنیم:

در Struct لازم نیست برای ایجاد شی جدید از دستور new استفاده کنیم، می توانم مثل متغیر ساده int آن را تعریف کنم و بعد به آن مقدار بدهیم:

فیلدها را در struct نمی توان مقدار دهی کرد. پس اینجا را خوب دقت کنید تا به id مقدار ندهیم ، فضایی داخل حافظه به آن اختصاص نمی دهد.

الان یک کلاس هم می نویسیم بالای Struct تا با هم مقایسه کنیم:

خوب دقت کنید class از نوع Reference Type است اما Struct  از نوع Value Type است .

یعنی اگر من دو تا کلاس داشته باشم داخل یکی از آنها تغییر اعمال کنم داخل آن یکی کلاس هم اعمال می شود. با یک مثال بیشتر متوجه می شویم:

داخل پنجره فرم شده تا با نوشتن یک کد تفاوت class و Struct در سی شارپ را بفهمیم:

برنامه را اجرا می کنیم و روی button1 کلیک می کنیم انتظار می رود عدد 10 را نمایش دهد.

Class Struct

امّا مشاهده می کنیم که عدد 20 را نمایش می دهد چون class رفرنسی است یعنی می رود به آدرسش نگاه می کند و چون خاصیت ارث بری هم دارد مقدار خودش را به آدرسش می دهد .

این بار برنامه را اجرا می کنیم و روی button2 کلیک می کنیم تا کد Struct اجرا شود:

Class Struct

مشاهده می کنیدکه عدد 10 را نمایش می دهد چون Struct از نوع Value تایپ است یعنی در Struct هر کدام مقدار خودشان را نمایش می دهند.

تفاوت Struct و class

1- در class به محض ساخته شدن شی فضایی به آن اختصاص داده می شود ولی در Struct حتی با وجود ساخته شدن شی فضایی به آن اختصاص داده نمی شود تا زمانی که مقداری داخل آن فیلد قرار گیرد.

2- در Struct لازم نیست که برای شی از کلمه ی new استفاده شود.

3- class رفرنس تایپ است اما value , Struct تایپ است در نتیجه چون کلاس از فیلد رفرنس استفاده می کند بنابراین حافظه بیشتری اشغال می کند. ولی Struct چون از فیلد رفرنس استفاده نمی کند حافظه کمتری اشغال می کند .

4- کلاس می تواند وراثت داشته باشد امّا Struct نمی تواند وراثت داشته باشد .

5- Struct نمی تواند تابع مخرب داشته باشد.

شباهت Struct و class

هر دو می توانند تابع سازنده داشته باشند.( البته خود Struct دارای تابع سازنده پیش فرض می باشد ولی اگر برای آن یک تابع سازنده تعریف کردیم باید تمام فیلدها را در آن مقداردهی اولیه کنیم. و تابع سازنده در Struct باید حداقل یک پارامتر داشته باشد.)