معرفی مفاهیم virtual, override, abstract و sealed در #C :
همانطور که مستحضر هستید کلمهی Polymorphism به معنای چندریختی است. در برنامهنویسی شیءگرا پلی مورفیسم اغلب به عنوان یک تغییردهنده رفتار یا یک واسط چند متدی مطرح میشود.
در زبان برنامهنویسی #C پلی مورفیسم به سه شکل ممکن پیادهسازی میشود:
1- استفاده از متدهای virtual و override کردن آنها در کلاس فرزند
2- بهرهگیری از متدهای abstract در کلاس والد
3- بهرهگیری از قابلیت واسطها یا Interfaceها ( واسطها در فصول بعدی به تفصیل توضیح داده خواهند شد)
متدهای virtual
فرض کنید یک کلاس والد به نام Shape دارید که در آن متدی به نام Draw تعریف شده است. متد Draw وظیفهی ترسیم یک شیء یا شکل را به عهده دارد. این متد در تمام کلاسهایی که از این کلاس مشتق میشوند قابل استفاده است. بنابراین کلاس Shape را به صورت زیر مینویسم:
public class Shape
{
public void Draw()
{
Console.WriteLine("Drawing the shape!");
}
}
حال باید کلاسهای فرزند مرتبط با این کلاس را تعریف کنیم بنابراین سه کلاس به نامهای Triangle و Rectangle و Circle را تعریف میکنیم که هر سه از کلاس والد یعنی Shape مشتق شده اند:
public class Rectangle : Shape
{
}
public class Triangle : Shape
{
}
public class Circle : Shape
{
}
هر سه کلاسی که در فوق تعریف کردیم میتوانند از متد Draw استفاده کنند زیرا این متد در کلاس اصلی والد تعریف شده است. حال اگر شیءای بسازیم آنگاه:
var rect = new Rectangle();
var tri = new Triangle();
var circ = new Circle();
rect.Draw();
tri.Draw();
circ.Draw();
Console.ReadKey();
در نهایت خروجی دستورات به صورت زیر است:
Drawing the shape!
Drawing the shape!
Drawing the shape!
اما این خروجی مورد پسند نیست و باید برای هر کلاس یک شکل کشیده شود در نهایت برای رفع این مشکل متد موجود در کلاس والد را به صورت virtual تعریف کرده و آن را درون کلاس فرزند override میکنیم. بنابراین تغییراتی در کلاس والد داده و متد Draw را به صورت زیر تعریف میکنیم:
public virtual void Draw()
{
Console.WriteLine("Drawing the shape!");
}
حال همین تغییرات را درون کلاسهای فرزند انجام میدهیم:
public class Rectangle : Shape
{
public override void Draw()
{
Console.WriteLine("Drawing rectangle!");
}
}
حال خروجی دستورات فوق به صورت زیر اصلاح میشوند:
Drawing rectangle!
Drawing the shape!
Drawing the shape!
همچنین اگر از کلمهی کلیدی base درون متد کلاس فرزند استفاده کنیم محتوا و متد کلاس پایه نمایش داده خواهد شد:
public override void Draw()
{
base.Draw();
}
استفاده از روش virtual و override تنها به متدها ختم نمیشود بلکه میتوان برای خصوصیات یا Property ها نیز این کار را انجام داد:
public class Human
{
public string FirstName { get; set; }
public string LastName { get; set; }
public virtual string FullSpecification
{
get { return FirstName + " " + LastName; }
}
}
public class Employee : Human
{
public string JobPosition { get; set; }
public override string FullSpecification
{
get { return base.FullSpecification + " with job position: " + JobPosition; }
}
}
معرفی کلاسها و متدهای abstract و sealed
هنگامیکه یک برنامه را مینویسیم و کلاسها و اعضای آن را مشخص میکنیم باید برای استفاده از کلاسها یک سری محدودیت بگذاریم. مثلا یک کلاس پایه داریم که این کلاس نباید توسط سایر کلاس ها مورد استفاده قرار بگیرد و تنها اعضای آن کلاس و کلاس هایی که از آن به ارث بردهاند، توانایی دسترسی به متدها و اعضای آن را دارند یا مثلا کلاسی را ایجاد کردهایم که با اعمال محدودیتهایی اجاره ایجاد کلاس فرزند از آن را نمیدهیم. برای اعمال همچین محدودیتهایی از کلاسها یا متدهایی با فرم abstract یا sealed استفاده میشود.
کلاسها و اعضاء abstract
به مثال قبلی باز میگردیم که یک کلاس والد به نام Shape داشتیم و از روی این کلاس سه فرزند ساخته بودیم:
public class Shape
{
public void Draw()
{
Console.WriteLine("Drawing the shape!");
}
}
public class Rectangle : Shape
{
}
public class Triangle : Shape
{
}
public class Circle : Shape
{
}
اگر به مثال بالا مراجعه کنید متوجه میشوید که کلاس Shape عملا برای ما کاربردی ندارد. به عبارت دیگر در طول برنامهی اصلی از آن استفاده نشده است. یعنی باید محدودیتی اعمال کنیم که از روی کلاس Shape شیءای ایجاد نشود. برای اینکار کافیست کلاس Shape را از نوع abstract تعریف کنیم. بنابراین طی یک تعریف کلی برای abstract داریم:
اگر کلاسی به صورت abstract ایجاد شود، در طول برنامه نمیتوان از روی آن شیءای ساخت.
به مثال زیر توجه کنید:
public abstract class Shape
{
public virtual void Draw()
{
Console.WriteLine("Drawing the shape!");
}
}
حال اگر بخواهیم از روی کلاس Shape یک شیء ایجاد کنیم با خطای زیر مواجه میشویم:
Cannot create an instance of the abstract class
یعنی نمیتوان از روی کلاسهایی که به صورت abstract تعریف شدهاند شیءای ایجاد کرد.
اما کاربردهای بیشتری از کلاس abstract انتظار میرود. در کلاسهای abstract میتوان به طور مشابه متدهایی را تعریف کرد که به صورت abstract باشند. این متدها تنها شامل signature هستند یعنی بدنهای نداشته و پس از تعریف آنها به علامت ; ختم میشوند. درصورتیکه یک متد به صورت abstract تعریف شود بدین گونه است که حتما باید آن را جهت استفاده در طی برنامه یا کلاس دیگر override کنند. برای مثال یکبار دیگر کلاس Shape را بازنویسی میکنیم:
public abstract class Shape
{
public abstract void Draw();
}
همانطور که ملاحظه میکنید متدی به نام Draw وجود دارد که بدنهای ندارد. علت این امر تعریف این متد به صورت abstract است. حال اگر کلاس فرزندی از کلاس Draw به ارث ببرد باید همواره متد درون آن به صورت abstract قرار بگیرد. بنابراین داریم:
public class Rectangle : Shape
{
public override void Draw()
{
Console.WriteLine("Drawing rectangle!");
}
}
کلاسها و اعضاء sealed
در بخش وراثت آموزش دادیم که همواره میتوان یک زنجیرهی وراثت در اختیار داشت مثلا کلاس B از کلاس A و کلاس C از کلاس B مشتق شود. حال درنظر بگیرید که میخواهیم به نحوی این زنجیرهی وراثت را قطع کنیم. برای اینکار باید کلاس موردنظر را از نوع sealed تعریف کنیم. بنابراین در طی یک تعریف کلی داریم:
کلاسهایی که به صورت sealed مورد استفاده قرار میگیرند، باعث حذف زنجیره وراثت میشوند.
به مثال زیر توجه کنید:
public class A
{
}
public sealed class B : A
{
}
در این کد کلاس B از نوع sealed تعریف شده است و این موضوع بدین معناست که هیچ کلاس دیگری نمیتواند از کلاس B مشتق شود و یا متدها و ویژگیهایی را به ارث ببرد. به عنوان مثال اگر کد زیر را پیاده سازی کنیم:
public class A
{
}
public sealed class B : A
{
}
public class C:B
{
}
آنگاه با خطای زیر روبهرو میشویم:
Cannot inherit from sealed class 'B'
بدین معنیست که شما نمیتوانید از یک کلاس که به صورت sealed تعریف شده است ویژگی یا متدی را به ارث ببرید.
یکی دیگر از کاربردهای عبارت sealed جلوگیری از override کردن یک متد است. به مثال زیر توجه کنید:
public abstract class Shape
{
public virtual void Draw()
{
Console.WriteLine("Drawing the shape!");
}
}
public class Rectangle : Shape
{
public sealed override void Draw()
{
Console.WriteLine("Drawing rectangle!");
}
}
این مثال بدین صورت عمل میکند که اگر کلاسی از کلاس Rectangle مشتق شد، دیگر قابلیت override کردن متد Draw را نداشته باشد زیر در متد موجود در کلاس Rectangle، متد به صورت sealed تعریف شده است.
فرق Class و Struct در #C :
Struct در سی شارپ یک جایگزین سبک حجم برای کلاس ها هستند. پس زمانی که می خواهیم نسخه های زیادی از یک داده را مقداردهی کنیم از Struct در سی شارپ استفاده می کنیم. تو این قسمت در مورد مفهوم Struct حرف بزنیم و این که Struct چه فرقی با Class دارد، برای ایجاد Struct ابتدا یک Class اضافه می کنیم
class ClassA
{
}
بعد از ایجاد کلاس نام کلیدی کلاس را پاک می کنیم و می توانیم نامی که برای کلاسمان در نظر گرفتیم هم پاک کنیم و نام دلخواه خودمان بگذاریم.
struct MyStruct
{
}
و مثل کلاس می توانیم فیلد و تابع داشته باشیم:
using System.Windows.Forms;
//—————————————–
struct MyStruct
{
public int id;
public void Show()
{
MessageBox.Show(id.ToString());
}
}
داخل پنجره فرم شده و از Struct نمونه می سازیم:
private void button1_Click(object sender, EventArgs e)
{
MyStruct S = new MyStruct();
S.id = 10;
S.Show();
}
سوال : اگر Struct تمام قابلیت های Class را دارد و هر دو مساوی هستند چرا هر دو را داخل زبان سی شارپ گذاشتند؟
این دو تا یک تفاوت هایی با هم دارند که الان با هم بررسی می کنیم:
در Struct لازم نیست برای ایجاد شی جدید از دستور new استفاده کنیم، می توانم مثل متغیر ساده int آن را تعریف کنم و بعد به آن مقدار بدهیم:
private void button1_Click(object sender, EventArgs e)
{
MyStruct S ;
S.id = 10;
}
فیلدها را در struct نمی توان مقدار دهی کرد. پس اینجا را خوب دقت کنید تا به id مقدار ندهیم ، فضایی داخل حافظه به آن اختصاص نمی دهد.
الان یک کلاس هم می نویسیم بالای Struct تا با هم مقایسه کنیم:
class MyClass
{
public int id;
}
struct MyStruct
{
public int id;
}
خوب دقت کنید class از نوع Reference Type است اما Struct از نوع Value Type است .
یعنی اگر من دو تا کلاس داشته باشم داخل یکی از آنها تغییر اعمال کنم داخل آن یکی کلاس هم اعمال می شود. با یک مثال بیشتر متوجه می شویم:
داخل پنجره فرم شده تا با نوشتن یک کد تفاوت class و Struct در سی شارپ را بفهمیم:
private void button1_Click(object sender, EventArgs e)
{
MyClass C1=new MyClass () ;
C1.id = 10;
MyClass C2 = C1;
C2.id = 20;
MessageBox.Show(C1.id.ToString());
}
private void button2_Click(object sender, EventArgs e)
{
MyStruct C1 ;
C1 .id = 10;
MyStruct C2 = C1;
C2.id = 20;
MessageBox.Show(C1.id.ToString());
}
برنامه را اجرا می کنیم و روی button1 کلیک می کنیم انتظار می رود عدد 10 را نمایش دهد.
امّا مشاهده می کنیم که عدد 20 را نمایش می دهد چون class رفرنسی است یعنی می رود به آدرسش نگاه می کند و چون خاصیت ارث بری هم دارد مقدار خودش را به آدرسش می دهد .
این بار برنامه را اجرا می کنیم و روی button2 کلیک می کنیم تا کد 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 باید حداقل یک پارامتر داشته باشد.)