وراثت یکی از سه مفهوم کلیدی در برنامه نویسی شی گرا است. وقتی کلاسهای مختلف دارای تعدادی ویژگیهای مشترک و مرتبط به یکدیگر هستند ، می توانیم از وراثت برای جلوگیری از تکرار استفاده کنیم.
در این مقاله، ما میخواهیم در مورد وراثت در #c صحبت کنیم. چرا وراثت مهم است و برای چه مواردی میتوانیم از آن استفاده کنیم.
این مقاله، سرفصلی از دوره آموزشی زیر میباشد:
- کلاسها و سازنده ها
- ویژگیها
- constant ،Static member ها و Extension Method ها
- Anonymous Type ها و Nullabel Type ها
- ساختارها
- شمارنده ها
- وراثت
- Interface ها
- کلاسهای Abstract
- Generic ها
- Queue, Stack, Hashtable
- Generic List و Dictionary
- Delegate ها
اگر می خواهید محتویات کامل این دوره را ببینید ، می توانید بر روی لینک سطح متوسطه #C کلیک کنید.
برای دانلود سورس، بر روی لینک سورس وراثت در #C کلیک کنید.
ما این مقاله را به قسمتهای زیر تقسیم میکنیم:
- استفاده از وراثت
- صدا زدن سازنده ها از کلاس پایه
- دسترسی به کلاسها
- تعریف متدها با کلمه کلیدی New
- استفاده از کلمه کلیدی New
- تعریف متدها با کلمه کلیدی Virtual
- تعریف متدها با کلمه کلیدی Override
- قوانینی که باید هنگام کار با متدهای چند ریختی (Polymorphic) رعایت کنید
استفاده از وراثت
ما میتوانیم وراثت را بین دو کلاس با استفاده از سینتکس زیر تعریف کنیم:
1 2 3 4 5 6 7 8 9 |
class DerivedClass: BaseClass { } class DerivedSubClass: DerivedClass { } |
چیزی که اینجا تفسیر میشود این است که کلاس DerivedSubClass از کلاس DerivedClass ارث بری میکند و همچنینDerivedSubClass از BaseClass نیز ارث بری میکند، چرا که DerivedClass از BaseClass ارث بری کرده است. به این ترتیب ، ما می توانیم ویژگی های کلاس را بین چندین کلاس به اشتراک بگذاریم ، حتی اگرچه یک کلاس فقط از یک کلاس پایه میتواند ارث بری کند.
حالا اجازه بدید مقداری ساختار وراثت پایه را با هم ایجاد کنیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public class Writer { public void Write() { Console.WriteLine("Writing to a file"); } } public class XMLWriter: Writer { public void FormatXMLFile() { Console.WriteLine("Formating XML file"); } } public class JSONWriter: Writer { public void FormatJSONFile() { Console.WriteLine("Formating JSON file"); } } |
در این مثال، کلاسهای XMLWriter
و JSONWriter
متدهای خودشان را دارند اما هر دوی آنها، متد ()Write از کلاس پایه Writer را نیز به اشتراک میگذارند.
بنابراین اگر ما یک آبجکت از نوع XMLWriter را ایجاد کنیم، قادر خواهیم بود به متدهای درون آن و همچنین متدهای کلاس پایه ای که از آن ارث بری کرده است دسترسی داشته باشیم:
1 2 3 4 5 6 7 8 9 |
class Program { static void Main(string[] args) { XMLWriter xmlWriter = new XMLWriter(); xmlWriter.FormatXMLFile(); xmlWriter.Write(); } } |
در مورد کلاس JSONWriter هم به همین شکل است.
صدا زدن سازنده ها از کلاس پایه
از درون کلاسهای مشتق شده، میتوانیم به سازنده یک کلاس پایه دسترسی داشته باشیم. استفاده از این مورد به دلیل initialize کردن تعدادی ویژگیها که بین کلاسهای مشتق شده به اشتراک گذاشته میشوند کاملا رایج است. برای اجرای آن، میتوانیم از کلمه کلیدی base استفاده کنیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
public class Writer { public string FileName { get; set; } public Writer(string fileName) { FileName = fileName; } public void Write() { Console.WriteLine("Writing to a file"); } } public class XMLWriter: Writer { public XMLWriter(string fileName) :base(fileName) { } public void FormatXMLFile() { Console.WriteLine("Formating XML file"); } } public class JSONWriter: Writer { public JSONWriter(string fileName) :base(fileName) { } public void FormatJSONFile() { Console.WriteLine("Formating JSON file"); } } class Program { static void Main(string[] args) { XMLWriter xmlWriter = new XMLWriter("xmlFileName"); xmlWriter.FormatXMLFile(); xmlWriter.Write(); Console.WriteLine(xmlWriter.FileName); JSONWriter jsonWriter = new JSONWriter("jsonFileName"); jsonWriter.FormatJSONFile(); jsonWriter.Write(); Console.WriteLine(jsonWriter.FileName); } } |
همانطور که میبینیم ما یک مقدار string را به سازنده کلاس مشتق شده و با استفاده از کلمه base پاس داده ایم. ما در واقع داریم آن مقدار string را به سازنده کلاس پایه پاس میدهیم. در آنجا مقدار را برای ویژگی FileName تنظیم می کنیم.
دسترسی به کلاسها
سلسله مراتب وراثت به این معنی است که کلاس XMLWriter (یا JSONWriter) ما نوع خاصی از کلاس Writer است و آن شامل تمام اعضایی که private نیستند و همچنین شامل ویژگیهای اضافی تعریف شده در داخل کلاس XML(JSON)Writer میباشد. اما تعدادی محدودیت در این سلسله مراتب وجود دارد.
نگاهی به مثال زیر بیندازیم:
1 2 3 4 |
XMLWriter xml = new XMLWriter("file.xml"); Writer writer = xml; writer.Write(); //ok Write is part of the Writer class writer.FormatXML(); //error FormatXML is not part of the Writer class |
این به این معنی است که اگر ما با آبجکت Writer به آبجکت XMLWriter یا JSONWriter مراجعه کنیم فقط میتوانیم به متدهای تعریف شده در داخل کلاس Writer دسترسی داشته باشیم.
یک محدودیت دیگر نیز وجود دارد. ما نمی توانیم یک آبجکت با درجه بالاتر را به یک آبجکت با درجه پایینتر اختصاص دهیم:
1 2 |
Writer writer = new Writer("any name"); XMLWriter xml = writer; //error |
اما ما میتوانیم این مشکل را با کلمه کلیدی as حل نماییم:
1 2 3 4 5 |
XMLWriter xml = new XMLWriter("any name"); Writer writer = xml; //writer points to xml XMLWriter newWriter = writer as XMLWriter; //this is ok now because writer was xml newWriter.FormatXMLFile(); |
تعریف متدها با کلمه کلیدی New
در یک پروژه واقعی، ما اغلب نیاز به ویژگیهای خیلی زیادی داریم و این معمولا منجربه وجود متدها، ویژگیها و … زیادی میشود. گاهی یافتن نام منحصر به فرد و معنی دار برای شناسه های ما بسیار دشوار است، به ویژه اگر سلسه مراتب وراثت را داشته باشیم. دیر یا زود ما نیاز پیدا میکنیم که از یک نام که هم اکنون توسط یکی از کلاسها در سطح مراتب بالاتر مورد استفاده قرار گرفته استفاده مجدد نماییم. اگر همچین موقعیتی برایمان پیش بیاید (دو متد با یک نام در کلاسهای پایه و مشتق شده داریم) آنگاه یک warning دریافت میکنیم:

استفاده از کلمه کلیدی New
یک متد در یک کلاس مشتق شده، یک متد در یک کلاس پایه با امضای یکسان را مخفی میکند. بنابراین همانطور که در تصویر بالا میتوانید ببینید، متد SetName هم در کلاس XMLWriter و هم در کلاس Writer وجود دارد. از آنجایی که کلاس XMLWriter از کلاس Writer ارث بری میکند در نتیجه از پیاده سازی متد SetName در کلاس Writer چشم پوشی میشود.
هرچند که کد ما در این حالت کامپایل شده و اجرا میشود اما بهتر است که این warning را جدی بگیریم. ممکن است یک کلاس دیگر از کلاس XMLWriter ارث بری کرده و متد SetName را پیاده سازی کند. توسعه دهنده ممکن است انتظار داشته باشد که متد SetName از کلاس Writer اجرا شود (زیرا XMLWriter از Writer ارث بری کرده است) در صورتی که اینطور نیست. متد SetName از کلاس Writer توسط متد SetName از کلاس XMLWriter نادیده گرفته شده است.
اگر ما در همچین موقعیتی قرار گرفتیم بهترین راه حل این است که امضاهای متد را تغییر دهیم. اما اگر مطمئن هستیم که رفتاری مانند همان متد مورد نظر را میخواهیم، میتوانیم از کلمه کلیدی new استفاده کنیم. کلمه new خیلی ساده به کامپایلر میگوید که ما از کاری که انجام میدهیم 100 درصد اطمینان داریم و دیگر یک پیام warning نمیخواهیم دریافت کنیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
public class Writer { public string FileName { get; set; } public Writer(string fileName) { FileName = fileName; } public void Write() { Console.WriteLine("Writing to a file"); } public void SetName() { Console.WriteLine("Setting name in the base Writer class"); } } public class XMLWriter: Writer { public XMLWriter(string fileName) :base(fileName) { } public void FormatXMLFile() { Console.WriteLine("Formating XML file"); } public new void SetName() { Console.WriteLine("Setting name in the XMLWriter class"); } } |
حالا در این حالت دیگر پیام warning نداریم.
تعریف متدها با کلمه کلیدی Virtual
برخی اوقات ما نمیخواهیم پیاده سازی یک متد از کلاس پایه با امضای یکسان را به عنوان یک متد در کلاس مشتق شده مخفی کنیم. بلکه چیزی که میخواهیم این است که یک فرصت برای پیاده سازی متفاوت یک متد با امضای یکسان در یک کلاس مشتق شده را فراهم کنیم. بنابراین ما میخواهیم متدمان از یک کلاس پایه را با متد مورد نظرمان داخل یک کلاس مشتق شده override کنیم.
متدی که قرار است override شود متد virtual نامیده میشود. وقتیکه ما در مورد override و hide کردن صحبت میکنیم نیاز است که تکلیف خود را با این دو اصطلاح شفاف سازی کینم. hide به این معنی است که میخواهیم پیاده سازی یک متد از کلاس پایه را نادیده بگریم. اما override به این معنی است که ما یک پیاده سازی متفاوت از یک متد از کلاس پایه را میخواهیم.
برای ایجاد یک متد virtual ما از کلمه کلیدی virtual استفاده میکنیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
public class Writer { public string FileName { get; set; } public Writer(string fileName) { FileName = fileName; } public void Write() { Console.WriteLine("Writing to a file"); } public void SetName() { Console.WriteLine("Setting name in the base Writer class"); } public virtual void CalculateFileSize() { Console.WriteLine("Calculating file size in a Writer class"); } } |
تعریف متدها با کلمه کلیدی Override
اگر ما یک متد را در کلاس پایه، به عنوان یک virtual تعریف میکنیم، میتوانیم یک متد در کلاس مشتق شده را با کلمه کلیدی override برای تعریف پیاده سازی دیگری از آن متد ایجاد کنیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public class XMLWriter: Writer { public XMLWriter(string fileName) :base(fileName) { } public void FormatXMLFile() { Console.WriteLine("Formating XML file"); } public new void SetName() { Console.WriteLine("Setting name in the XMLWriter class"); } public override void CalculateFileSize() { Console.WriteLine("Calculating file size in the XMLWriter class"); } } |
اگر بخواهیم میتوانیم نمونه اصلی آن متد را در یک کلاس مشتق شده با کلمه کلیدی base فراخوانی کنیم:
1 2 3 4 5 6 7 8 9 10 |
public class XMLWriter: Writer { ... public override void CalculateFileSize() { base.CalculateFileSize(); Console.WriteLine("Calculating file size in the XMLWriter class"); } } |
تمام این اقدامات وراثت و پیاده سازی های مختلف متد به همراه کلمات کلیدی ذکر شده، نام چندریختی منحصربه فرد خودشان را دارند.
قوانینی که باید هنگام کار با متدهای چند ریختی (Polymorphic) رعایت کنید
چند قانون مهم وجود دارد که هنگام تعریف متدهای چندریختی با استفاده از کلمات virtual و override نیاز است که رعایت کنیم:
- ما نمیتوانیم یک متد virtual را به صورت private تعریف کنیم. چرا که هدف این است که آن در یک کلاس مشتق شده ظاهر گردد، بنابراین private کردن آن بی معنی است. به طور مشابه متدهای overridden نیز نمیتوانند private باشند، زیرا یک کلاس مشتق شده نمیتواند سطح دسترسی یک متدی که از آن ارث بری کرده است را تغییر دهد.
- امضاهای متدهای virtual و overridden باید یکسان باشد.
- ما میوانیم یک متد virtual را فقط override کنیم. اگر ما سعی کنیم که یک متدی که کلمه virtual ندارد را override کنیم، آنگاه با خطا مواجه میشویم.
- اگر ما از کلمه override استفاده نکنیم آنگاه متد مورد نظر را override نکرده ایم بلکه فقط داریم آن را hide میکنیم که اگر این واقعا همان چیزی است که ما میخواهیم، سپس بهتر است که از کلمه new استفاده کنیم.
- یک کلاس override شده، یک کلاس virtual نیز است. بنابراین خودش در یک کلاس مشتق شده دیگر نیز میتواند دومرتبه override شود.
نتیجه گیری
در این مقاله ما یاد گرفتیم:
- وراثت چیست و چطور از آن استفاده کنیم.
- چطور از کلمات new, virtual و override استفاده کنیم.
- در مورد قوانین چندریختی در زبان #C
در مقاله بعدی، ما در مورد اینترفیسها در #C صحبت میکنیم.