زبان سی شارپ (C#) یک زبان شی گرا می باشد و در آموزش برنامه نویسی شی گرا در سی شارپ (C#) قصد داریم با مفاهیم شی گرایی (OOP) در سی شارپ آشنا بشیم. اما برنامه نویسی شی گرا چیست؟ چرا ما از زبان های شی گرا استفاده می کنیم؟ مفاهیمی مانند شی و کلاس در زبان های شی گرا چه کاربردی دارند و چندین سوال دیگر که در ادامه سری آموزشی سی شارپ قصد داریم به این سوالات پاسخ داده و شما را با برنامه نویسی شی گرا و مفاهیم های مرتبط با آن آشنا کنیم. پس در ادامه دوره آموزشی سی شارپ با من همراه باشید.

تعریف شی گرایی

قبل از هر کاری، بهتر است با مفهوم برنامه نویسی شی گرا و این که به چه زبانی شی گرایی گفته می شود آشنا شویم. برنامه نویسی شی گرا، بر خلاف زبان های Procedural که همه چیز در آن بر اساس روال ها تعریف می شدند، مدل سازی نرم افزار بر اساس اشیاء انجام می شود.

برای یادگیری و آموزش سی شارپ ( آموزش برنامه نویسی سی شارپ ) حرفه ای به این لینک مراجعه کنید

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

  • خصوصیات یا Properties: خصوصیات مجموعه ای از صفات هستند که یک شی را توصیف می کنند. برای مثال شی ای با نام انسان را در نظر بگیرید، این شی یکسری خصوصیات دارد مانند رنگ مو، قد، وزن، رنگ چشم و غیره. تمامی این پارامترها که به توصیف یک شی می پردازند تحت عنوان خصوصیت یا Property شناخته می شوند.
  • رفتارها یا Behaviors: هر شی علاوه بر خصوصیات، شامل یکسری رفتارها می باشد، این رفتارها در حقیقت کاریست که یک شی می تواند انجام دهد. دوباره شی انسان را در نظر بگیرید، این شی می تواند نگاه کند، صحبت کند یا بشنود. رفتارها با خصوصیات تفاوت دارند و به کاری گفته می شوند که یک شی می تواند انجام دهد.

در زبان های برنامه نویسی شی گرا نیز ما باید به شناسایی موجودیت ها و اشیاء مورد استفاده در برنامه بپردازیم و خصوصیات و رفتارهای آن را تعریف کنیم. فرض کنید تصمیم داریم برنامه ای برای مدیریت یک کتابخانه بنویسیم. برنامه کتابخانه شامل یکسری اشیاء می باشد مانند:

  1. عضو کتابخانه
  2. اپراتور نرم افزار کتابخانه
  3. دسته بندی کتاب (که همان قفسه هایی که کتاب ها در آن دسته بندی می شوند می باشد)
  4. کتاب

پس از شناسایی موجودیت ها باید خصوصیت ها و رفتارهای آن ها را شناسایی کنیم. برای مثال شی عضو کتابخانه را در نظر بگیرید. این شی شامل یکسری خصوصیت ها به شرح زیر می باشد:

  1. کد عضویت
  2. نام
  3. نام خانوداگی
  4. شماره ملی
  5. نام پدر
  6. جنسیت

همچنین هر عضو یکسری رفتارهایی دارد که مختص به عملیات های کتابخانه می باشد. برای مثال عضو کتابخانه می تواند رفتارهای زیر را داشته باشد:

  1. دریافت کتاب
  2. پس دادن کتاب
  3. ورود به کتابخانه
  4. خروج از کتابخانه

پس از آنکه رفتارها و خصوصیات اشیاء یک برنامه شناسایی شدند، باید نسبت به پیاده سازی آنها در نرم افزار اقدام کنیم که در قسمت بعدی در مورد پیاده سازی اشیاء و تعریف خصوصیات و رفتارهای آنها توضیح خواهیم داد.

مفاهیم اساسی در برنامه نویسی شی گرا

برای ادامه مباحث مربوط به آموزش برنامه نویسی شی گرا در سی شارپ، لازم است که با چهار مفهوم اساسی در زبان های برنامه شی گرا آشنا شویم. این چهار مفهوم، ارکان اساسی و ستون های برنامه نویسی شی گرا می باشند که در زیر به بررسی هر یک از انها خواهیم پرداخت:

دید انتزاعی (Abstraction)

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

اما آیا تمامی این خصوصیات در سیستم به کار می آید؟ در مورد رفتارهای یک شی نیز همین موضوع صدق می کند. مفهوم Abstraction به ما می گوید زمان بررسی یک موجودیت، تنها خصوصیات و رفتارهایی باید در تعریف موجودیت لحاظ شوند که مستقیماً در سیستم کاربرد دارند. در حقیقت Abstraction مانند فیلتری عمل می کنند که تنها خصوصیات و رفتارهای مورد استفاده در برنامه ای که قصد نوشتن آن را داریم از آن عبور می کنند.

پنهان سازی (Encapsulation)

فرض کنید ماشین جدیدی خریداری کرده اید، پشت فرمان ماشین می نشینید و ماشین را استارت می زنید. استارت زدن ماشین خیلی ساده است، قرار دادن سوئیچ و چرخاندن آن و روشن شدن ماشین. اما آیا پروسه ای که داخل ماشین طی شده برای روشن شدن نیز همینقدر ساده است؟

صد در صد، عملیات های بسیار دیگری اتفاق می افتد تا ماشین روشن شود. اما شما تنها سوئیچ را چرخانده و ماشین را روشن میکنید. در حقیقت پیچیدگی عملیات روشن شدن ماشین از راننده ماشین پنهان شده است. به این عملیات Encapsulation یا پنهان سازی پیچیدگی پیاده سازی عملیات های درون یک شی می گویند.

وراثت (Inheritance)

می توان گفت Inheritance یا وراثت اصلی ترین مفهوم در برنامه نویسی شی گرا است. زمانی که شما خوب این مفهوم را درک کنید 70 درصد از مفاهیم برنامه نویسی شی گرا را درک کرده اید. برای درک بهتر این مفهوم مثالی میزنیم. تمامی انسان های متولد شده بر روی کره خاکی از یک پدر و مادر متولد شده اند.

در حقیقت این پدر و مادر والدین انسان هستند. زمانی که انسانی متولد می شود یکسری خصوصیات و ویژگی ها را از والدین خود به ارث می برد، مانند رنگ چشم، رنگ پوست یا برخی ویژگی های رفتاری. در برنامه نویسی شی گرا به زبان سی شارپ نیز به همین صورت می باشد.

زمانی که شما موجودیت را طراحی می کنید، می توانید برای آن یک کلاس Base یا والد در نظر بگیرید که شی فرزند تمامی خصوصیات و رفتارهای شی والد را به ارث خواهد برد. مهمترین ویژگی وراثت، استفاده مجدد از کدهای نوشته شده است که حجم کدهای نوشته شده را به صورت محسوسی کاهش می دهد. در بخش های بعدی در مورد این ویژگی به صورت کامل توضیح خواهیم داد.

Polymorphism چیست؟

در فرهنگ لغت این واژه به معنای چند ریختی ترجمه شده است. اما در برنامه نویسی شی گرا چطور؟ خیلی از افراد با این مفهوم مشکل دارند و درک صحیحی از آن پیدا نمی کنند. مفهوم Polymorphism رابطه مستقیمی با Inheritance دارد. یعنی شما ابتدا نیاز دارید مفهوم وراثت را خوب درک کرده و سپس به یادگیری Polymorphism بپردازید.

باز هم برای درک مفهوم Polymorphism یک مثال از دنیای واقعی میزنیم. در کره خاکی ما انسان های مختلفی در کشور های مختلف و شهر های مختلف با گویش های مختلف زندگی می کنند. اما تمامی این ها انسان هستند. در اینجا انسان را به عنوان یک شی والد و انسان چینی، انسان ایرانی و انسان آمریکایی را به عنوان اشیاء فرزند که از شی انسان مشتق شده اند یا والد آنها کلاس انسان می باشد را در نظر بگیرید.

کلاس انسان رفتاری را تعریف می کند به نام صحبت کردن. اما اشیاء فرزند آن، به یک صورت صحبت نمی کنند، انسان ایرانی با زبان ایرانی، چینی با زبان چینی و آمریکایی با زبان آمریکایی صحبت می کند. در حقیقت رفتاری که در شی والد تعریف شده، در شی های فرزند مجدد تعریف می شود یا رفتار آن تغییر می کند.

این کار مفهوم مستقیم Polymorphism می باشد. در زبان های برنامه نویسی شی گرا، Polymorphism به تغییر رفتار یک شی در اشیاء فرزند آن گفته می شود. در زبان سی شارپ این کار با کمک تعریف متدها به صورت virtual و override کردن آنها در کلاس های فرزند انجام می شود.

همچنین Polymorphism با کمک Interface ها قابل پیاده سازی است که در بخش های بعدی در مورد این ویژگی ها به صورت کامل صحبت خواهیم کرد.در این بخش مقدمه ای بر مفاهیم اولیه برنامه نویسی شی گرا داشتیم، در لیست زیر مباحثی که در طول دوره برنامه نویسی شی گرا با آنها آشنا خواهیم شد را مشاهده می کنید:

  1. آشنایی با class ها و object ها
  2. تعریف فیلد ها و Property ها در کلاس ها
  3. نحوه تعریف رفتار ها برای کلاس ها با کمک متدها
  4. تعریف سازنده ها یا Constructor برای کلاس ها
  5. فیلد های readonly
  6. نوع های بدون نام (Anonymous Types)
  7. آشنایی با structs و تفاوت آن با کلاس
  8. آشنایی با Reference Types و Value Types تفاوت آنها و حافظه های Stack و Heap
  9. وراثت و کاربرد آن در برنامه نویسی شی گرا
  10. انواع مختلف وراثت (Is-A و Has-A)
  11. کلمات کلیدی this و base
  12. متدهای virtual و مبحث Polymorphism
  13. کلاس های Abstract و Sealed
  14. آشنایی با Access Modifiers
  15. تعریف Interface ها و کاربرد آنها در برنامه نویسی شی گرا
  16. بررسی برخی از Interface های موجود در کتابخانه دات نت
  17. سایر مباحث (Indexer ها، Extension Method ها، بررسی کلاس Object و آشنایی با مفاهیم Boxing و UnBoxing)

با اتمام مباحث ذکر شده، وارد قسمت Generics که در نسخه دوم دات نت به آن اضافه شده است خواهیم شد. امیدوارم که تا انتهای این دوره آموزشی با بنده همراه باشید.

کلاس و شی

در قسمت قبلی آموزش زبان سی شارپ، به بررسی مفاهیم اولیه برنامه نویسی شی گرا پرداختیم. در ادامه، بر اساس مفاهیم گفته شده در قسمت قبل، به صورت عملی با نحوه تعریف کلاس ها، ایجاد اشیاء از روی کلاس ها و همچنین نحوه تعریف فیلد، خصوصیت و رفتارها برای اشیاء آشنا خواهیم شد.

همانطور که در قسمت قبل گفتیم، زمانی که قصد نوشتن برنامه ای به صورت شی گرا را داریم، باید موجودیت های مورد استفاده را در برنامه مدل سازی کنیم. این موجودیت ها همان اشیاء هستند که در سیستم مورد استفاده قرار میگیرند. اما شیوه مدل سازی و استفاده از اشیاء چگونه خواهد بود؟ در اینجا باید با دو مفهوم آشنا شویم: 1. کلاس ها و 2. اشیاء.

  1. کلاس: نمونه ای از یک شی که داخل برنامه طراحی می شود را کلاس می گویند. برای اینکه با مفهوم کلاس بیشتر آشنا شوید یک مثال از دنیای واقعی می زنیم. فرض کنید تصمیم به ساخت یک خانه دارید. اولین چیزی که به آن نیاز خواهید داشت نقشه خانه ایست که تصمیم دارید بسازید. نقشه یک طرح اولیه و مفهومی از ساختمان به شما می دهد و بعد از روی نقشه اقدام به ساخت خانه می کنید. نقشه شامل تمامی بخش های خانه است، اطاق پذیرایی، آشپزخانه، حمام، سرویس بهداشتی و سایر بخش ها. اما فقط یک نقشه در اختیار دارید. نمی توانید از اطاق پذیرایی داخل نقشه استفاده کنید. کلاس دقیقاً معادل نقشه ای است که شما برای ساختمان خود کشیده اید. کلاس یک نمونه اولیه از موجودیت ایست که باید اشیاء از روی آن ساخته شوند.
  2. شی: باز هم به سراغ مثال قبلی می رویم. بعد از کشیدن نقشه ساختمان شما باید اقدام به ساخت خانه کنید.

بعد از اتمام عملیات ساخت، خانه شما قابل سکونت بوده و شما می توانید از آن استفاده کنید. همچنین از روی یک نقشه ساختمانی می توان چندین ساختمان ساخت. شی دقیقاً معادل همان مفهوم ساختمانی است که از روی نقشه ساخته شده است.

شما بعد از اینکه کلاس را تعریف کردید، باید از روی کلاس شی بسازید تا بتوانید از آن استفاده کنید. در حقیقت کلاس به صورت مستقیم قابل استفاده نیست، مگر اینکه شامل اعضای static باشد که در بخش های بعدی با آنها آشنا خواهیم شد.

همچنین می توان از روی یک کلاس، یک یا چندین شی تعریف کرد.حال که با مفاهیم اولیه کلاس و شی آشنا شدید، بهتر است با نحوه تعریف کلاس و ساخت شی آشنا شویم. تعریف کلاس بوسیله کلمه کلیدی class در زبان C# انجام می شود. ساختار کلی این دستور به صورت زیر است:

{access-modifier} class {name} { } 

قسمت access-modifier سطح دسترسی به کلاس را تعیین می کند. ما زمانی که اقدام به تعریف کلاس یا هر قطعه کدی در زبان c# می کنیم، می توانیم سطح دسترسی به آن کد را تعیین کنیم. اما سطح دسترسی به چه معناست؟ در قسمت های اولیه آموزش گفتیم که زمان ایجاد یک پروژه به زبان C#، برای شما یک solution ایجاد شده که هر solution می تواند شامل چندین پروژه باشد. برای مثال، کلاس یا اعضای یک کلاس را تعریف می کنیم، می توانیم مشخص کنیم که این کلاس از کدام قسمت های پروژه قابل دسترس باشد. سطوح دسترسی زیر در زبان سی شارپ تعریف شده اند:

  1. private: این سطح دسترسی مشخص می کند که قطعه کد تعریف شده تنها داخل خود پروژه یا Scope مربوطه قابل دسترس باشند. برای مثال کلاسی که به صورت private تعریف شده باشد، تنها داخل همان پروژه قابل دسترس بوده و از سایر پروژه هایی که در solution تعریف شده قابل دسترس نخواهد بود، یا اعضای کلاسی که به صورت private تعریف شده اند، تنها در Scope همان کلاس که بین علامت های {} می باشد قابل دسترس خواهند بود.
  2. public: کدهایی که با این سطح دسترسی مشخص شده باشند، در تمامی قسمت های پروژه و سایر پروژه ها قابل دسترس خواهند بود.
  3. internal: سطوح دسترسی internal، تنها داخل همان پروژه قابل دسترس بوده و سایر پروژه ها به آنها دسترسی نخواهند داشت. این سطح دسترسی برای اعضای کلاس ها کاربرد زیادی دارد.
  4. protected: این سطح دسترسی زمانی که از مفهوم inheritance استفاده کنیم کاربرد دارد. در قسمت وراثت این سطح دسترسی را به تفصیل مورد بررسی قرار خواهیم داد.
  5. internal protected: همانند قسمت protected، این دسترسی نیز در قسمت وراثت توضیح داده خواهد شد که تلفیقی از دسترسی های internal و protected می باشد.

بعد از access-modifier، با کلمه کلیدی class می گوییم که قصد تعریف یک کلاس را داریم و بعد از کلمه کلیدی class در قسمت name نام کلاس را مشخص می کنیم. نام کلاس باید همیشه بر اساس قاعده PascalCase نام گذاری شود. ما دو شیوه نام گذاری داریم:

  1. camelCase: در این شیوه نام گذاری، کاراکتر ابتدای هر کلمه باید با حروف بزرگ نوشته شود غیر از کلمه اول. مانند: newEmployee، sampleDictionary.
  2. PascalCase: در این شیوه نام گذاری، کاراکتر ابتدای هر کلمه باید با حروف بزرگ نوشته شود، مانند: SampleDictionary، NewEmployee

حال، تصمیم داریم یک کلاس با نام Person تعریف کنیم. در قسمت های بعدی به این کلاس خصوصیات و رفتارهای مورد نظر را اضافه خواهیم کرد. برای تعریف کلاس، بر روی نام پروژه در پنجره Solution Explorer، با موس راست کلیک کرده و از منوی ظاهر شده از قسمت گزینه Class... را انتخاب می کنیم:

 

i1

بعد از انتخاب این گزینه، نام کلاس مورد نظر را در پنجره Add New Item وارد کرده و روی دکمه Add کلیک می کنیم. در اینجا نام Person را وارد می کنیم. بعد از انجام این کار، فایل جدیدی با نام Person.cs به پروژه ما اضافه می شود:

 

i2

 

i3

اگر بر روی فایل Person.cs دوبار کلیک کنیم، محتویات فایل مورد نظر به صورت زیر نمایش داده خواهد شد:

 

i4

نکته: در قسمت معرفی ابزارهای این مجموعه آموزشی، درباره ابزاری به نام Resharper صحبت کردیم. در صورتی که این ابزار را نصب کرده باشید، برای تعریف کلاس جدید، کافیست پروژه ای که قصد تعریف کلاس داخل آن را درید، در پنجره Solution Explorer انتخاب کرده و کلیدهای Alt+Insert را فشار دهید. با اینکار منوی زیر نمایش داده می شود:

 

i5

بعد از انتخاب گزینه کلاس از منوی ظاهر شده، نام کلاس از شما پرسیده شده و کلاس به پروژه شما اضافه می شود.

به سراغ محتویات فایل اضافه شده برویم:

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;  namespace CSharpOOP {     class Person     {     } } 

قسمت using مربوط به استفاده کلاس هایی است که در namespace های دیگر تعریف شده اند. namespace ها برای دسته بندی کدهای پروژه مورد استفاده قرار میگیرند، در حقیقت شما می توانید از لحاظ کاربردی کدهای خود را در زبان سی شارپ بوسیله namespace تقسیم بندی کنید.

برای مثال، در کد بالا، کلاس Person، در namespace یا فضای نام CSharpOOP تعریف شده است. زمانی که پروژه ای ایجاد می کنید، فضای نام پیش فرض بر اساس نام پروژه ایجاد شده و تمام کدهای شما داخل این فضای نام تعریف خواهند شد.

همچنین کلیه کلاس هایی که به صورت پیش فرض در دات نت تعریف شده اند، در فضای نام System قرار دارند. در حقیقت System فضای نام پایه برای کلیه کلاس های موجود در دات نت می باشد. همچنین می توان برای هر فضای نام یک فضای نام زیر مجموعه تعریف کرد که این جداسازی بوسیله کاراکتر . انجام می شود. برای مثال، برای فضای نام CSharpOOP می خواهیم یک فضای نام زیر مجموعه با نام DataTools تعریف کنیم:

namespace CSharpOOP.DataTools { } 

به کد کلاس Person برگردیم. در ادامه کد، فضای نام CSharpOOP مشخص شده که داخل آن کلاس Person تعریف شده است. اگر دقت کنید، این کلاس access-modifier ندارد. کدهایی که برای آنها access-modifier مشخص نشده باشد، به صورت پیش فرض private در نظر گرفته می شوند.

بعد از تعریف کلاس بوسیله {} محدوده کلاس مشخص شده است که کدهای مربوط به کلاس داخل آن نوشته می شوند.خوب تا اینجا، ما با شیوه تعریف یک کلاس ساده آشنا شدیم. در مرحله بعد، باید از روی این کلاس یک شی بسازیم. ساختار کلی تعریف شی به صورت زیر است:

{class-name} {object-name} = new {class-name}(); 

در قسمت class-name، نام کلاس را مشخص می کنیم، برای مثال Person و در قسمت object-name، نام شی مورد نظر را مشخص می کنیم. در حقیقت object-name یک متغیر است که به شی ما اشاره می کند. بعد از علامت انتساب یا = باید عملیات ساخت شی را انجام دهیم.

بوسیله کلمه کلیدی new می گوییم که تصمیم به ساخت یک شی جدید داریم و در مقال آن نام کلاسی که می خواهیم از روی آن شی بسازیم را می نویسیم. دقت کنید که بعد از نوشتن نام کلاس در مقال کلمه کلیدی new باید () حتماً نوشته شود، در غیر اینصورت با پیغام خطا مواجه خواهید شد. با توضیحات بالا، می توان گفت عملیات ساخت شی در دو مرحله انجام می شود:

  1. تعریف متغیری که شی داخل آن نگهداری می شود (دستورات قبل از عملیات انتساب).
  2. ساخت شی و قرار دادن آن داخل متغیر مربوطه (دستورات بعد از عملیات انتساب).

حال از روی کلاس Person یک شی ایجاد می کنیم. کد متد Main در فایل Program.cs را به صورت زیر تغییر دهید:

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;  namespace CSharpOOP {     class Program     {         static void Main(string[] args)         {             Person person = new Person();         }     } } 

بوسیله کد بالا، عملیات ساخت شی انجام شد. همانطور که قبلا گفتیم، ما می توانیم چندین شی با نام های متفاوت از روی یک کلاس ایجاد کنیم:

Person person1 = new Person(); Person person2 = new Person(); Person person3 = new Person(); 

دقت کنید، کلاس Program در فایل Program.cs نیز داخل فضای نام CSharpOOP قرار دارد. شما می توانید در فایل های متفاوت فضای نام همنام داشته باشید، بدین معنی که کلیه کدها در همان فضای نام قرار خواهند گرفت. در صورتی که شما در متد Main نام فضای نام CSharpOOP را تایپ کنید و پس از آن کلید . را بزنید، لیستی که از محتویات آن فضای نام برای شما نمایش داده خواهد شد:

 

i6

اما فرض کنید، کد ما در فایل Person.cs، در فضای نام دیگری با نام CSharpOOP.Entities تعریف شده بود:

namespace CSharpOOP.Entities {     class Person     {     } } 

در این حالت، زمانی که شما در فایل Program.cs و متد Main، تصمیم دارید از روی کلاس Person شی بسازید، باید آدرس کامل فضای نام را نیز هنگام ساخت شی مشخص کنید، زیرا فضای نام کلاس های Program و Person دیگر یکسان نیستند:

CSharpOOP.Entities.Person person = new CSharpOOP.Entities.Person(); 

اما در اینجا نکته ای وجود دارد، چون ابتدای فضای نام کلاس های Program و کلاس Person یکسان می باشد، یعنی فضای نام Entities زیر مجموعه CSharpOOP قرار دارد و کلاس Program نیز در فضای نام CSharpOOP تعریف شده، می توان از نوشتن قسمت اول فضای نام یعنی CSharpOOP خودداری کرد:

Entities.Person person = new Entities.Person(); 

دقت کنید، اگر فضای نام را برای ایجاد شی ننویسیم، با پیغام خطا مواجه خواهیم شد. اما راهی وجود دارد که آدرس کامل کلاس را ننویسیم، برای اینکار از دستور using استفاده می کنیم که در بالا نیز به آن اشاره شد. دستور using کلیه کدهای داخل یک فضای نام را داخل فضای نام جاری قابل دسترس می کند. برای مثال بالا، کافیست در قسمت using فایل Program.cs، دستور زیر را بنویسیم:

using CSharpOOP.Entities; 

با نوشتن دستور بالا، دیگر نیازی به نوشتن آدرس فضای نام هنگام ساخت شی نخواهد بود. نمونه دیگر استفاده از دستور using، استفاده از دستورات کلاس Console می باشد که در قسمت های قبل با آن زیاد کار کردیم. کلاس Console داخل فضای نام System که فضای نام پایه کلیه کلاس های دات نت می باشد تعریف شده.

اما بدلیل اینکه در ابتدای فایل Program.cs دستور using System; نوشته شده است، کافیست تنها نام کلاس Console را بنویسیم و نیازی به نوشتن آدرس کامل آن به صورت System.Console نمی باشد.شما می توانید داخل یک فایل چندین کلاس را تعریف کنید. برای مثال، در فایل Program.cs می توانید بعد از اتمام کد کلاس Program.cs، اقدام به تعریف کلاس Person نمایید:

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using CSharpOOP.Entities;  namespace CSharpOOP {     class Program     {         static void Main(string[] args)         {             Entities.Person person = new Entities.Person();         }     }      class Person     {              } } 

اما بهتر است برای هر کلاس، یک فایل جداگانه در نظر بگیرید تا ساختار مناسب برای پروژه ای که تصمیم به انجام آن دارید حفظ شود.یکی دیگر از قابلیت های موجود در Solution Explorer، قابلیت پوشه بندی فایل ها داخل پروژه می باشد. برای مثال، می توانید کلاس های مربوط به موجودیت های برنامه را داخل یک پوشه قرار دهید.

برای اینکار، بر روی پروژه راست کلیک کرده، از قسمت Add گزینه New Folder را انتخاب کنید. با اینکار پوشه جدیدی به پروژه شما اضافه می شود که می توانید برای آن یک نام دلخواه انتخاب کنید:

 

i7

در صورتی که ابزار Resharper را نصب کرده باشید، با زدن کلید های Alt+Insert بر روی پروژه داخل Solution Explorer از منوی ظاهر شده گزینه New Folder را برای افزودن پوشه جدید انتخاب کنید.پس از تعریف پوشه، با انتخاب آن و تکرار مراحل قبلی برای ایجاد کلاس، می توانید داخل آن پوشه یک فایل جدید ایجاد کنید. برای مثال، پوشه ای با نام Entities داخل پروژه تعریف کرده و کلاسی با نام Car داخل آن تعریف کنید. بعد از اینکار محتویات فایل شما به صورت زیر خواهد بود:

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;  namespace CSharpOOP.Entities {     public class Car     {               } } 

به یک نکته توجه کنید که فضای نام یا namespace کلاس Car به صورت خودکار CSharpOOP.Entities انتخاب شده است، زیرا کلاس داخل پوشه Entities که در پروژه CSharpOOP قرار دارد اضافه شده. در صورتی که شما داخل یک پوشه، پوشه جدیدی اضافه کرده و داخل آن یک کلاس اضافه کنید، آدرس فضای نام مبتنی بر نام آن پوشه انتخاب خواهد شد.

پس نکته بعدی که باید مد نظر داشته باشید، زمانی که قصد دارید کدهای خود را بوسیله فضاهای نام دسته بندی کنید، حدالامکان برای آنها پوشه ایجاد کنید، اجباری به اینکار نیست، اما برای حفظ ساختار و نظم پروژه اینکار توصیه می شود.در این قسمت از سری آموزش سی شارپ، با مفاهیم کلاس، شی، فضاهای نام، دستور using و پوشه بندی فایل ها داخل پروژه آشنا شدیم. در قسمت بعدی آموزش با نحوه تعریف خصوصیت و رفتار برای کلاس ها و شیوه استفاده از آنها بوسیله اشیاء ساخته شده آشنا خواهیم شد.

فیلد و متد

در قسمت قبلی آموزش زبان سی شارپ با مفاهیمی مانند کلاس، شی، فضای نام و دستور using آشنا شدیم. در این قسمت ابتدا شیوه تعریف کلاس و شی را مروری کوتاه کرده و سپس به بررسی شیوه تعریف فیلد و رفتار در کلاس خواهیم پرداخت.با کلاس Person شروع می کنیم که در قسمت قبل کلاسی با نام Person ایجاد کردیم. شیوه تعریف این کلاس به صورت زیر بود:

public class Person { } 

بعد از ایجاد کلاس باید از روی آن یک شی یا اصطلاحاً Instance یا نمونه بسازیم:

Person person = new Person(); 

دقت کنید، می توانیم از کلمه کلیدی var که در قسمت های ابتدایی این آموزش با آن آشنا شدیم استفاده کنیم:

var person = new Person(); 

کلمه کلیدی var را می توان هنگام ایجاد متغیرها و ایجاد شی از روی کلاس ها استفاده کرد. ما تا اینجا تنها یک کلاس تعریف کردیم. اما این کلاس هیچ خصوصیت یا رفتاری ندارد. زمانی که کلاسی تعریف می کنید باید اعضا یا Member های آن کلاس را مشخص کنید. هر کلاس به طور کلی می تواند شامل موارد زیر باشد:

  1. Field
  2. Property
  3. Method
  4. Indexer
  5. Event
  6. Nested Type

آشنایی با Field ها

در ابتدا به بررسی مفهوم Field پرداخته و شیوه دسترسی به اعضای کلاس را خواهیم گفت. فیلد متغیری است که داخل یک کلاس تعریف شده و امکان ذخیره مقداری را به ما می دهد. این متغیر می تواند از نوع داده های اولیه دات نت یا کلاسی باشد که تعریف کرده ایم. شیوه تعریف Field دقیقاً مشابه تعریف متغیر می باشد. با این تفاوت که ما زمان تعریف یک Field امکان استفاده از کلمه کلیدی var را نداریم. برای کلاس Person دو فیلد با نام های FirstName و LastName تعریف می کنیم:

public class Person {     public string FirstName;     public string LastName; } 

دقت کنید که قبل از مشخص کردن نوع داده فیلد، سطح دسترسی آن مشخص شده است. در اینجا ما فیلدها را از نوع public تعریف کردیم. یعنی این فیلدها خارج از scope کلاس و بیرون از پروژه نیز قابل دسترس میی باشند. حال که فیلدهای مورد نظر را داخل کلاس تعریف کردیم

می توانیم بعد از ایجاد شی از کلاس، به این فیلدها دسترسی داشته باشیم. برای دسترسی به فیلدها بعد از نوشتن نام شی از کاراکتر . استفاده می کنیم تا لیستی از اعضای آن کلاس برای نمایش داده شود. تصویر زیر مربوط به کد ما در متد Main است:

 

وب سایت توسینسو

کد Main را به صورت زیر تغییر دهید:

var person = new Person();  person.FirstName = "Hossein"; person.LastName = "Ahmadi";  Console.WriteLine(person.Firstname + " " + LastName); 

کد بالا از سه بخش نشکیل شده است.

  1. ایجاد یک شی از روی کلاس Person
  2. مقدار دهی فیلد ها
  3. استفاده از فیلدها برای نمایش خروجی

در همه شرایط، زمانی که میخواهیم به یک عضو کلاس دسترسی داشته باشیم، باید از کاراکتر . استفاده کنیم، برای مثال در فیلدهای بالا، چه زمانی که قصد مقدار دهی فیلد را داریم، چه زمانی که می خواهیم مقداری از فیلد را بخوانیم.بوسیله فیلدها می توان خصوصیات یک کلاس را تعریف کرد، در مورد خصوصیات در قسمت قبل توضیحاتی دادیم.

مورد بعدی تعریف رفتار برای کلاس می باشد. رفتارها همان متدها در کلاس ها هستند. شیوه تعریف متد برای کلاس، تفاوتی با تعریفاتی که در قسمت قبل از متد گفتیم ندارد، به جز اینکه متدهایی که داخل یک کلاس تعریف می شوند static نیستند.

می توانیم رفتارهای یک کلاس را static تعریف کنیم، مانند کاری در کلاس Program در قسمت های قبلی می کردیم، اما در حال حاضر، متدها به صورت static تعریف نمی شوند. با اعضای static کلاس ها در قسمت های بعد به صورت کامل آشنا خواهیم شد

اما به صورت خلاصه تفاوت اعضای static و غیر static در این است که برای دسترسی به اعضای static نیازی به ایجاد شی از روی کلاس نیست، مانند کلاس Console که برای استفاده از اعضای آن مانند WriteLine شی ای از کلاس Console ایجاد نمی کنیم، اما برای استفاده از اعضای غیر static باید حتماً از روی کلاس یک شی ایجاد شود.

تعریف رفتار یا Method برای کلاس

در ادامه می خواهیم برای کلاس Person یک رفتار تعریف کنیم. موجودیت Person چه کارهایی می تواند انجام دهد؟ برای مثال یک شخص می تواند صحبت کند، پس رفتاری با نام Speak تعریف می کنیم و در آن یک پیغام مناسب در خروجی چاپ می کنیم:

public class Person {     public string FirstName;     public string LastName;      public void Speak()     {         Console.WriteLine("Hello, my name is " + FirstName + " " + LastName + ".");     } } 

در کد بالا، متد یا رفتار Speak پیغامی را به همراه نام و نام خانوداگی مشخص شده توسط فیلدها در خروجی نمایش می دهد. دقت کنید، داخل کلاس هم ما می توانیم از اعضای یک کلاس استفاده کنیم. خیلی وقت ها رفتارهای یک کلاس وابسته به مقادیر خصوصیات یک کلاس هستند.

برای مثال شما یک ماشین را در نظر بگیرید، خصوصیتی داریم به نام دنده ماشین، یک خصوصیت دیگر به نام دور موتور و رفتاری داریم به نام حرکت. رفتار حرکت بر اساس دنده و دور موتور سرعت خواهد گرفت. حال کد Main را به صورت زیر تغییر می دهیم:

var person = new Person(); person.FirstName = "Hossein"; person.LastName = "Ahmadi"; person.Speak(); Console.ReadLine(); 

مثال عملی: کلاس Calculator

در ادامه یک مثال واقعی تر را بررسی می کنیم. کلاسی می نویسیم با نام Calculator که دو فیلد با نام های FirstNumber و SecondNumber دارد. همچنین چهار متد با نام های Sum و Substract و Multiply و Divide تعریف می کنیم که بر اساس مقادیر FirstNumber و SecondNumber، در خروجی حاصل جمع، تفریق، ضرب یا تقسیم را نمایش دهد. کلاس Calculator را به صورت زیر تعریف کنید:

public class Calculator {     public int FirstNumber;     public int SecondNumber;      public int Sum()     {         return FirstNumber + SecondNumber;     }      public int Substract()     {         return FirstNumber - SecondNumber;     }      public int Multiply()     {         return FirstNumber * SecondNumber;     }      public int Divide()     {         return FirstNumber / SecondNumber;     } } 

دقت کنید، رفتارهای کلاس همگی مقدار بازگشتی از int دارند. در ادامه متد Main را به صورت زیر تغییر دهید:

var calculator = new Calculator(); calculator.FirstNumber = 12; calculator.SecondNumber = 17; Console.WriteLine("Sum: " + calculator.Sum()); Console.WriteLine("Substract: " + calculator.Substract()); Console.WriteLine("Multiply: " + calculator.Multiply()); Console.WriteLine("Divide: " + calculator.Divide()); Console.ReadLine(); 

کد بالا، خروجی حاصل جمع، تفریق، ضرب و تقسیم اعداد 12 و 17 را در خروجی چاپ می کند. تا اینجا با مبحث رفتارها و خصوصیات در کلاس ها آشنا شدیم. در بخش بعدی در باره شیوه کنترل دسترسی به مقادیر یک خصوصیات در کلاس ها یعنی مبحث Property ها صحبت خواهیم کرد

Property ها

در قسمت قبلی آموزش، در مورد فیلدها و نحوه تعریف آنها در کلاس آشنا شدیم، اما گاهی اوقات نیاز داریم که بر روند مقداردهی و گرفتن مقدار یک فیلد نظارت داشته باشیم. زمانی که یک فیلد تعریف می کنیم، عملیات انتساب مقدار و گرفتن مقدار به صورت مستقیم از داخل فیلد انجام شده و امکان انجام هیچ گونه نظارتی بر روی این عملیات ها وجود ندارد. برای رفع این مشکل، دو راه وجود دارد:

  1. استفاده از متدها برای ست کردن و گرفتن مقدار از فیلد.
  2. استفاده از Property ها

افرادی که با زبان جاوا آشنا هستند با متدهای get و set در کلاس ها آشنایی دارند. این متدها عملیات خواندن و نوشتن در فیلدها را برای ما انجام می دهند. مثالی از زبان سی شارپ می زنیم. کلاس Person را در نظر بگیرید:

public class Person {     public string FirstName;     public string LastName; } 

این کلاس، ما زمانی که یک شی از این کلاس می سازیم به صورت مستقیم فیلدها را مقدار دهی کرده یا مقدار آنها را می خوانیم. اما برای کنترل دسترسی به فیلدها، ابتدا باید سطح دسترسی فیلدها را به private تغییر بدیم. زمانی که یک عضو کلاس که در اینجا فیلدها هستند را به private تغییر می دهیم، آن عضو تنها داخل همان کلاس قابل دسترس خواهد بود. برای اولین قدم، کلاس Person را به صورت زیر تغییر می دهیم:

public class Person {     private string firstName;     private string lastName; } 

به نام گذاری فیلدها دقت کنید، زمانی که فیلدها به private تغییر کردند، نام گذاری بر اساس قاعده camelCase انجام می شود. این قاعده برای کلیه فیلدهای private کلاس ها حکم می کند. البته الزامی به این کار نیست، اما برای رعایت اصول کد نویسی بهتر است از این قواعد پیروی کنیم.

بعضی از برنامه نویس ها ابتدای نام فیلدهای private از کاراکتر __ استفاده می کنند. این موضوع کاملاً دلخواه می باشد، اما سعی کنید در نام گذاری فیلدها public و private تفاوت قایل شوید.در قدم بعدی باید بتوانیم در خارج از کلاس، عملیات خواندن و مقدار دهی فیلد را انجام دهیم. در روش اول گفتیم که از متدهای get و set برای اینکار استفاده می کنیم. کلاس Person را به صورت زیر تغییر می دهیم:

public class Person {     private string firstName;     private string lastName;      public string GetFirstName()     {         return firstName;                 }      public void SetFirstName(string value)     {         firstName = value;     }      public string GetLastName()     {         return lastName;     }      public void SetLastName(string value)     {         lastName = value;     } } 

در تصویر زیر، لیست نمایش داده شده برای شی ای از کلاس Person را مشاهده می کنید:

 

i1

حال می توانیم عملیات خواندن و نوشتن فیلدها را در خارج از کلاس بوسیله این دو متد انجام دهیم:

var person = new Person();  person.SetFirstName("Hossein"); person.SetLastName("Ahmadi");  Console.WriteLine(person.GetFirstName() + " " + person.GetLastName()); Console.ReadLine(); 

حال فرض کنید، یک فیلد باید تنها خواندنی باشد، برای اینکار کافیست بخش Set را از کلاس حذف کنید یا فرض کنید عملیات نوشتن باید تنها در داخل خود کلاس انجام شود و از بیرون کلاس دسترسی نوشتن باید بسته شود، برای این کار کافیست که دسترسی متد Set برای فیلد مورد نظر را به private تغییر دهیم. در مثال زیر عملیات نوشتن برای فیلد firstName به صورت private تعریف شده و فیلد lastName قابلیت نوشتن ندارد:

public class Person {     private string firstName;     private string lastName;      public string GetFirstName()     {         return firstName;                 }      private void SetFirstName(string value)     {         firstName = value;     }      public string GetLastName()     {         return lastName;     } } 

استفاده از Property ها

اما در زبان سی شارپ به صورت دیگری می توان این کنترل را انجام داد و آن استفاده از Property ها می باشند. ساختار کلی property ها به صورت زیر می باشد:

{access-modifier} {data-type} {property-name} {     [access-modifier] get     {         // body for get value     }     [access-modifier] set     {         // body for set value     } } 

اما بررسی هر یک از قسمت های ساختار Property:

  1. access-modifier: سطح دسترسی به Property را تعیین می کند. Property نیز مانند فیلد می تواند سطح دسترسی داشته باشد.
  2. data-type: نوع Property که یکی از Data Type های دات نت یا کلاسی که به صورت دستی نوشته شده باشد.
  3. property-name: نام Property، برای نام گذاری Property ها همیشه از قاعده PascalCase استفاده کنید، سطح دسترسی تفاوتی ندارد، همیشه PascalCase تعریف کنید.
  4. بدنه get: این بدنه، دقیقاً معادل متد Get ایست که در قسمت قبلی تعریف کردیم. شما داخل بدنه get هر دستوری را می توانید بنویسید، در حقیقت این بدنه مانند یک متد عمل کرده و زمانی که شما مقدار Property را می خوانید (مثلاً برای چاپ با دستور

WriteLine) بدنه get اجرا می شود. دقت کنید بدنه get حتماً باید مقداری را با دستور return بر گرداند. همچنین این بدنه می تواند دارای access-modifier باشد، یعنی سطح دسترسی خواندن مقدار را مشخص می کند. در صورتی که سطح دسترسی را مشخص نکنید به صورت پیش فرض public در نظر گرفته می شود.

  1. بدنه set: این بدنه، دقیقاً معادل متد Set در مثال قبلی است. زمانی که شما مقداری را داخل Property ست می کنید، بدنه set اجرا می شود. داخل بدنه set پارامتر پیش فرضی وجود دارد به نام value که مقدار ست شده داخل Property داخل آن قرار

گرفته و شما می توانید به آن از داخل بدنه set دسترسی داشته باشید. همچنین می توان برای بدنه set سطح دسترسی را مشخص کرد. در صورتی که سطح دسترسی را مشخص نکنید به صورت پیش فرض public در نظر گرفته می شود.به مثال کلاس Person بر می گردیم. تصمیم داریم عملیات هایی که در بوسیله متدهای Get و Set انجام دادیم را با Property ها پیاده سازی کنیم. کلاس Person را به صورت زیر تغییر دهید:

public class Person {     private string firstName;     private string lastName;      public string FirstName     {         get { return firstName; }         set { firstName = value; }     }      public string LastName     {         get { return lastName; }         set { lastName = value; }     } } 

در کد بالا، دو Property با نام های FirstName و LastName تعریف کردیم که عملیات خواندن و نوشتن از فیلدهای مربوطه را انجام می دهند. نتیجه اعضای نمایش داده شده برای کلاس Person هنگام استفاده از نمونه یا Instance آن را در تصویر زیر مشاهده

می کنید:

 

i2

در ادامه کد متد Main را به صورت زیر تغییر دهید:

var person = new Person();  person.FirstName = "Hossein"; person.LastName = "Ahmadi";  Console.WriteLine(person.FirstName + " " + person.LastName); Console.ReadKey(); 

در کد بالا، زمانی که مقدار Hossein را داخل فیلد FirstName ست می کنیم، بدنه set مربوط به خاصیت FirstName اجرا می شود که در این بدنه ما مقدار value که همان مقدار ست شده در هنگام استفاده از کلاس است را داخل فیلد firstName قرار می دهیم. برای LastName هم به همین صورت است. شاید بپرسید پارامتر value کجا تعریف شده است؟

این پارامتر به صورت پیش فرض برای بدنه set وجود دارد که نگه دارنده مقدار ست شده داخل Property است.همچنین زمانی که داخل دستور WriteLine مقدار FirstName یا LastName را می خوانیم، بدنه get مربوط به همان Property اجرا می شود. در حقیقت Property ها واسطی میان فیلدها و استفاده کننده از کلاس ها هستند.

مانند یک انبار دار که عملیات کنترل ورود و خروج از انبار را کنترل می کند و عملیات تحویل دادن کالا یا گرفتن کالا و قرار دادن آن در انبار را انجام می دهد.برای نوشتن Property ها، حتماً نیازی به تعریف Field برای آنها نیست، شما می توانید هر کدی را برای بدنه get یا set بنویسید. برای مثال، می خواهیم به کلاس Person یک Property تعریف کنیم که نام کامل شخص را بر گرداند. نام این خاصیت را FullName می گذاریم:

public class Person {     private string firstName;     private string lastName;      public string FirstName     {         get { return firstName; }         set { firstName = value; }     }      public string LastName     {         get { return lastName; }         set { lastName = value; }     }      public string FullName     {         get { return FirstName + " " + LastName; }     } } 

حال کد Main قسمت قبلی را می توان به صورت زیر تغییر داده و از Property جدیدی که تعریف کردیم استفاده کنیم:

var person = new Person();  person.FirstName = "Hossein"; person.LastName = "Ahmadi";  Console.WriteLine(person.FullName); Console.ReadKey(); 

نگاهی دوباره به کلاس Person و خاصیت FullName می کنیم، اگر دقت کرده باشید این خاصیت تنها بدنه get را دارد و بدنه set را برای آن ننوشتیم. دلیل این امر آن است که FullName تنها برای ترکیبی از firstName و lastName را بر میگرداند. در صورتی که بخواهیم مقداری داخل FullName بریزیم، با پیغام خطا مواجه می شویم.

 

i3

Property هایی که بدنه get را ندارند Write-Only و آنهایی که بدنه set را ندارند Read-Only می گوییم. همچنین همانطور که قبلاً هم گفتیم می توانیم علاوه بر خود Property برای هر یک از بدنه های get و set نیز سطح دسترسی مشخص کنیم. برای مثال میخواهیم خاصیت FirstName تنها داخل خود کلاس قابلیت نوشتن داشته باشد، برای اینکار کافیست بدنه set را به صورت private تعریف کنیم:

public string FirstName {     get { return firstName; }     private set { firstName = value; } } 

Automatic Properties چیست؟

گاهی اوقات، Property که تعریف می کنیم تنها عملیات خواندن و نوشتن یک فیلد را کنترل می کند. برای مثال، کلاس Person را در نظر بگیرید:

public class Person {     private string firstName;      public string FirstName     {         get { return firstName; }         set { firstName = value; }     } } 

کد مربوط به خصوصیت بالا را می توان به شکل زیر نیز نوشت:

public class Person {     public string FirstName { get; set; } } 

کامپایلر بعد از کامپایل کد بالا، به صورت خودکار یه فیلد برای خاصیت نوشته شده تعریف کرده و بدنه get و set آن را به صورت خودکار می نویسد. از مزیت های Auto-Property ها حجم کد کمتر و البته قابلیت کنترل دسترسی به عملیات های خواندن و نوشتن Property ها می باشد. مثال بالا را جوری تغییر می دهیم که خاصیت FirstName تنها داخل کلاس قابل نوشتن باشد:

public class Person {     public string FirstName { get; private set; } } 

به این نکته توجه داشته باشید، زمانی که از Auto-Property ها استفاده می کنید، حتماً باید get و set را بنویسید، در غیر اینصورت پیغام خطا دریافت خواهید کرد. البته این مشکل در نسخه 6 زبان سی شارپ برطرف شده است.در این قسمت با Property ها آشنا شدیم و متوجه شدیم که چگونه می توان عملیات خواندن و نوشتن مقادیر یک کلاس را کنترل کرد. در قسمت بعدی با مفهوم سازنده ها در کلاس ها آشنا شده و به بررسی حالت های مختلف ایجاد شی از روی کلاس ها خواهیم پرداخت

سازنده ها یا Contructors

در قسمت قبلی آموزش در مورد خصوصیات یا Property ها و نحوه صحیح استفاده از آنها در کلاس ها صحبت کردیم. در این بخش در مورد سازنده ها یا Constructors، مقدار دهی اولیه اشیاء (Object Initialization) و نوع های بدون نام (Anonymous Types) صحبت می کنیم.زمانی که شما کلاسی را تعریف می کنید

این کلاس حاوی یکسری خصوصیات و یکسری رفتارها یا همان متدها می باشد. شما بعد از ایجاد شی، خصوصیات را مقدار دهی کرده و از شی استفاده می کنید. اما چندین راه دیگر برای مقدار دهی خصوصیات و آماده سازی اولیه کلاس وجود دارد. ما در این قسمت دو روش مختلف را بررسی می کنیم:

  1. مقدار دهی اولیه شی یا Object Initialization
  2. استفاده از سازنده ها یا Constructors

مقدار دهی اولیه با کمک Object Initialization

در این روش، شما زمانی که اقدام به ایجاد یک شی می کنید، می توانید مقادیر خصوصیات و فیلدهای آن را مشخص کنید. کلاس Person را در نظر بگیرید:

public class Person {     public string FirstName;     public string LastName; } 

به صورت پیش فرض، شما یک شی از کلاس ساخته و خصوصیات آن را مقدار دهی می کنید:

var person = new Person(); person.FirstName = "Hossein"; person.LastName = "Ahmadi"; 

مقدار دهی اولیه شی کار ساده ایست، کافیست پس از نوشتن () بعد از نام کلاس در قسمت new بین علامت های {} مقادیر خصوصیات را مشخص کنیم:

var person = new Person() {     FirstName = "Hossein",     LastName = "Ahmadi" }; 

با این کار مقادیر FirstName و LastName در کلاس Person مقدار دهی اولیه خواهند شد. می توانید در این حالت، از نوشتن () صرفنظر کنید:

var person = new Person {     FirstName = "Hossein",     LastName = "Ahmadi" }; 

دقت کنید، در هنگام مقدار دهی اولیه قابلیت صدا زدن متدهای کلاس را نخواهید داشت و تنها می توانید فیلدها، خصوصیات و برخی اعضای دیگر که در قسمت های بعدی با آن آشنا خواهیم شد را مقدار دهی کنید.

سازنده ها یا Constructors

اگر دقت کرده باشید، زمانی که شی ای را تعریف می کنیم، بعد از نوشتن نام کلاس بعد از کلمه new از () استفاده می کنیم، مشابه زمانی که تصمیم به صدا زدن یک متد دارید. دلیل اینکار، پروسه ایست که سی شارپ برای ایجاد کردن کلاس ها انجام می دهد.

زمانی که شما شی ای از یک کلاس ایجاد می کنید، سی شارپ قسمتی با نام سازنده یا Constructor را برای آن کلاس صدا می زند. این سازنده یک متد می باشد که می تواند بدون پارامتر یا با پارامتر باشد و داخل آن کدی نوشته می شود که می خواهیم در هنگام ایجاد شی اجرا شود.

با یک مثال ساده سازنده ها را بررسی می کنیم. کلاس Person را در نظر بگیرید، برای این کلاس یک سازنده تعریف می کنیم که مقادیر FirstName و LastName را به عنوان ورودی گرفته و خصوصیات مربوطه را مقدار دهی می کند:

public class Person {     public Person(string firstName, string lastName)     {         this.FirstName = firstName;         this.LastName = lastName;     }      public string FirstName { get; set; }     public string LastName { get; set; } } 

در کد بالا، قسمت سانده برای کلاس Person با دو پارامتر تعریف شده است. به شیوه تعریف سازنده دقت کنید، ابتدا سطح دسترسی به سازنده مشخص شده، سپس نام کلاس نوشته شده که برای سازنده ها، این نام دقیقاً باید معادل نام کلاس باشد، سپس پارامترهای مورد نظر و بعد از آن ها بدنه سازنده. دقت کنید سازنده ها مقدار بازگشتی ندارند. با توضیحات گفته شده می توان ساختار کلی سازنده را به صورت زیر بیان کرد:

{access-modifier} {class-name}([parameters]) {     // constructor body } 

همچنین در بدنه سازنده بالا، به کلمه کلیدی this دقت کنید. کلمه کلیدی this به شی جاری که روی کلاس ساخته شده است اشاره می کند. فرض کنید شما ده ها شی از روی یک کلاس ساخته اید، زمانی که یک رفتار را صدا می زنید و داخل آن رفتار از کلمه کلیدی this استفاده می کنید، this به همان شی ای اشاره می کند که رفتار در آن صدا زده شده است. در این سازنده نیز کلمه کلیدی this به شی ای اشاره می کند که سازنده برای آن صدا زده شده.پس از تعریف سازنده می توان هنگام ایجاد شی، مقادیر مورد نظر را به سازنده ارسال کرد:

var person = new Person("Hossein", "Ahmadi"); 

با اجرای کد بالا، خصوصیت های FirstName و LastName هنگام ایجاد شی، مقدار دهی خواهند شد. اما باید به یک نکته در اینجا توجه داشت، زمانی که شما سازنده ای به همراه پارامتر برای یک کلاس تعریف می کنید، دیگر نمی توانید از کلاس بدون ارسال پارامتر در سازنده شی بسازید.

دلیل این موضوع، عدم وجود سازنده ای به نام سازنده پیش فرض یا Default Constructor می باشد. سازنده پیش فرض، سازنده ایست که هیچ پارامتری را به عنوان ورودی نمی گیرد. زمانی که شما سازنده ای برای یک کلاس تعریف نکرده اید، آن کلاس به صورت پیش Default Constrcutor برایش تعریف شده است.

اما زمانی که اقدام به ایجاد یک سازنده برای کلاس کردید، اگر می خواهید از آن کلاس بدون ارسال پارامتر برای سازنده شی بسازید، باید سازنده پیش فرض را به صورت دستی برای آن کلاس بنویسید:

public class Person {     public Person()     {     }      public Person(string firstName, string lastName)     {         this.FirstName = firstName;         this.LastName = lastName;     }      public string FirstName { get; set; }     public string LastName { get; set; } } 

شما می توانید سطح دسترسی به سازنده ها را مشخص کنید، برای مثال، حالتی پیش می آید که می خواهد یک سازنده فقط داخل همان کلاس در دسترس باشد، یعتی شما از یک کلاس داخل خودش، مثلاً داخل یک رفتار، می خواهید یک شی بسازید. برای این کار، می توانید سطح دسترسی سازنده مد نظر را private تعریف کنید. برای مثال:

public class Person {     public Person()     {     }      private Person(string firstName, string lastName)     {         this.FirstName = firstName;         this.LastName = lastName;     }      public Person CreateObject(string firstName)     {         return new Person(firstName, null);     }      public string FirstName { get; set; }     public string LastName { get; set; } } 

در کد بالا، یک متد یا رفتار برای کلاس تعریف کردیم با نام CreateObject. این رفتار یک شی از روی خود کلاس Person می سازد و پارامتر ارسالی به متد CreateObject را به سازنده ارسال می کند. اما خارج از شی، دیگر نمی توانیم از سازنده ای که دو پارامتر را به عنوان ورودی می گیرد استفاده کنیم، زیرا این سازنده با سطح دسترسی private تعریف شده است.همانند متدها، سازنده ها می توانند overload داشته باشند، یعنی چند سازنده با signature های متفاوت. برای مثال، کلاس Person را به صورت زیر تغییر می دهیم:

public class Person {     public Person()     {     }      public Person(string firstName)     {         this.FirstName = firstName;     }      public Person(string firstName, string lastName)     {         this.FirstName = firstName;         this.LastName = lastName;     }      public string FirstName { get; set; }     public string LastName { get; set; } } 

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

زنجیره سازنده ها یا Constrcutor Chaining

زمانی که شما چندین سازنده دارید، می توانید از کد های نوشته شده داخل یک سازنده در سازنده دیگر استفاده کنید. برای اینکار از قابلیت constructor chaining استفاده می شود. با یک مثال ادامه می دهیم، در کد قبلی سه سازنده داشتیم، سازنده پیش فرض، سازنده ای که تنها FirstName را می گرفت و سازنده ای که FirstName و LastName را به عنوان پارامتر می گرفت. در سازنده دوم، می توان از سازنده سوم جهت مقدار دهی استفاده کرد. برای این کار، کد بالا را به صورت زیر تغییر می دهیم:

public class Person {     public Person()     {     }      public Person(string firstName) : this(firstName,null)     {     }      public Person(string firstName, string lastName)     {         this.FirstName = firstName;         this.LastName = lastName;     }      public string FirstName { get; set; }     public string LastName { get; set; } } 

سازنده دوم ما به صورت زیر تغییر کرده است:

public Person(string firstName) : this(firstName,null) { } 

دقت کنید، بعد از بستن پرانتز پس از علامت : از کلمه کلیدی this مانند یک متد استفاده کرده ایم، در این روش، سازنده کلاس صدا زده شده و به عنوان پارامتر اول، firstName که در سازنده تعریف شده را ارسال کرده و عنوان پارامتر دوم مقدار null را ارسال کرده ایم.

قابلیت constrcutor chaining، در کاهش تعداد خطوط نوشته در برنامه کمک زیادی به ما می کند.به این نکته توجه داشته باشید که نمی تواند سازنده را به صورت متد از داخل کلاس جایی غیر از خود سازنده ها صدا زد. همچنین سازنده ها تنها برای مقدار دهی خصوصیات استفاده نمی شوند، شما می توانید هر کدی را داخل سازنده بنویسید.

نوع های بدون نام یا Anonymous Types

نوع های بدون نام، به ما این امکان را می دهند تا شی ای بدون تعریف کلاس ایجاد کنیم. این شی تنها می تواند شامل خصوصیات باشد و قابلیت تعریف رفتار برای آن را نخواهیم داشت. در مثال زیر یک شی بدون نام ایجاد کرده ایم که سه خصوصیت با نام FirstName و LastName و Age دارد:

var anonymous = new {     FirstName = "Hossein",     LastName = "Ahmadi",     Age = 29 };  Console.WriteLine(anonymous.FirstName + " " + anonymous.LastName); 

همانطور که در کد مشاهده می کنید، کافیست بعد از کلمه کلیدی new بلافاصه به سراغ عملیات Object Initialization برویم و نیازی به نوشتن نام کلاس نیست. نوع های بدون نام کاربردهای زیادی در بخش LINQ دارند که در همین وب سایت دوره آموزشی LINQ توسط بنده نوشته شده و در این دوره آموزشی نیز مروری کوتاه بر این قابلیت خواهیم داشت. در بخش بعدی آموزش، مبحث وراثت یا Inheritance که مهمترین مبحث در زمینه برنامه نویسی شی گرا می باشد را آغاز خواهیم کرد.

وراثت

یکی از مباحث بسیار مهم در برنامه نویسی شی گرا، مبحث وراثت یا Inheritance است. در قسمت مقدمه برنامه نویسی شی گرا، در مورد این مبحث به صورت مختصر صحبت کردیم. در ادامه تصمیم داریم که مبحث را بیشتر مورد بررسی قرار دهیم و با نحوه کاربرد آن در زبان سی شارپ بیشتر آشنا شویم. مبحث وراثت از این نظر مهم است که به شما کمک می کند از کدهای نوشته شده مجدداً استفاده کنید و در نتیجه حجم کده نوشته شده در برنامه شما به صورت محسوسی کاهش پیدا کند.

تعریف وراثت یا Inheritance و پیاده سازی آن در زبان C#

همانطور که در مقدمه مبحث برنامه نویسی شی گرا خدمت دوستان توضیح دادم، وراثت به معنی به ارث بردن یکسری خصوصیات و رفتار بوسیله فرزند از والد است. در برنامه نویسی شی گرا، زمانی که صحبت از وراثت می کنیم، در حقیقت می خواهیم برای یک کلاسی، یک کلاس والد مشخص کنیم. وراثت در برنامه نویسی شی گرا کاربردهای بسیاری دارد، به صورتی که اصلی ترین و بنیادی ترین قابلیت در برنامه نویسی شی گرا نام برده می شود. قبل از شروع به نکته زیر توجه کنید:

زمانی که کلاس A به عنوان والد کلاس B معرفی می شود، یعنی کلاس B فرزند کلاس A می باشد، می گوییم کلاس B از کلاس A مشتق شده است. در طول این دوره از واژه مشتق شده به تکرار استفاده خواهیم کرد.در ابتدا با شیوه کلی استفاده از وراثت در کلاس ها آشنا می شویم. فرض کنید کلاسی داریم با نام A:

public class A {          } 

حال تصمیم داریم کلاسی تعریف کنیم با نام B که از کلاس A مشتق شده است، یعنی تمامی خصوصیات و رفتارهای کلاس A را به ارث می برد. برای اینکار کلاس B را به صورت زیر تعریف می کنیم:

public class B : A { } 

بوسیله دستور بالا، کلاس A به عنوان کلاس والد کلاس B در نظر گرفته خواهد شد. گفتیم یکی از مزایای استفاده از وراثت در برنامه نویسی شی گرا، استفاده مجدد از کدهایی است که در کلاس والد تعریف شده است. در مثال بالا، کد کلاس A خصوصیتی با نام Item1 و Item2 تعریف می کنیم:

public class A {     public string Item1 { get; set; }     public string Item2 { get; set; } } 

به دلیل اینکه کلاس B از کلاس A مشتق شده است، می توانیم از خصوصیت های Item1 و Item2 برای کلاس B استفاده کنیم:

B obj = new B(); obj.Item1 = "Hossein Ahmadi"; obj.Item2 = "ITPro.ir"; 

حال، کلاس سومی تعریف می کنیم با نام C. این کلاس نیز از کلاس A مشتق می شود:

public class C : A { } 

زمانی که شی ای از کلاس C بسازیم، میبینیم که خصوصیات Item1 و Item2 برای این شی کلاس C نیز وجود دارند، در حقیقت ما این خصوصیات را تنها یکبار در کلاس A تعریف کردیم و با قابلیت وراثت از این کدها برای کلاس های B و C مجدداً استفاده کردیم. زمانی که کلاسی یک یک کلاس والد مشتق می شود، علاوه بر اینکه دارای خصوصیات و رفتارهای کلاس های والد می باشد، می توان برای کلاس فرزند خصوصیات و رفتارهای جدید تعریف کرد. کلاس C را که در بالا تعریف کردیم به صورت زیر تغییر می دهیم:

public class C : A {     public string Item3 { get; set; } } 

حال زمانی که ما شی ای از روی کلاس C بسازیم علاوه بر خصوصیت های Item1 و Item2 که در کلاس A تعریف شده اند، به خصوصیت دیگری نیز نام Item3 که در کلاس C تعریف شده دسترسی خواهیم داشت:

var instanceOfC = new C(); instanceOfC.Item1 = "Hossein Ahmadi"; instanceOfC.Item2 = "ITPro.ir"; instanceOfC.Item3 = "C# Course"; 

وراثت در زبان سی شارپ، به صورت درختی می باشد، یعنی زمانی که کلاس C از کلاس A مشتق شد می توان کلاس D را نوشت که از کلاس C مشتق شده است:

public class D : C {     public string Item4 { get; set; } } 

در مثال بالا، کلاس D علاوه بر خصوصیات کلاس A و کلاس C خصوصیات مربوط به خودش را نیز شامل می شود. در حقیقت زنجیره وراثت را در این مثال مشاهده می کنید. در مثال های بالا، کلاس ها تنها شامل Property بودند، زمانی که شما برای کلاسی یک رفتار تعریف می کنید، کلاس های فرزند آن رفتار را نیز به ارث می برند:

public class A {     public string Item1 { get; set; }     public string Item2 { get; set; }          public void PrintItem1()     {         Console.WriteLine(Item1);     } } 

حال شی ای از کلاس D می سازیم و رفتار PrintItem1 را صدا می زنیم:

var obj = new D(); d.PrintItem1(); 

در قسمت های قبلی دیدیم که کلاس D از کلاس C مشتق شده است و خود کلاس C از کلاس A. پس کلیه خصوصیات و رفتارهای کلاس A برای سطوح پایین تر وراثت قابل دسترس هستند.

کلمه کلیدی base

در قسمت های قبلی، در مورد کلمه کلیدی this توضیح دادیم و گفتیم که این کلمه کلیدی به شی ای اشاره می کند که از روی کلاس ساخته شده. کلمه کلیدی دیگری وجود دارد با نام base که اشاره به کلاس والد دارد. برای مثال، کلاس B را به صوورت زیر تغییر می دهیم:

public class B : A {     public void PrintParentItems()     {         Console.WriteLine(base.Item1 + " " + base.Item2);     } } 

در مثال بالا، کلمه کلیدی base به کلیه اعضای والد اشاره می کند. زمانی که شما از کلمه کلیدی base داخل کلاس استفاده می کنید، تنها اعضای کلاس والد به شما نمایش داده شده و اعضای کلاس فرزند به شما نمایش داده نمی شوند. در قسمت های بعدی با کاربردهای دیگر کلمه base آشنا می شویم.

تبدیل کلاس های مشتق شده به کلاس والد

زمانی که شما از روی یک کلاس، شی ای می سازید باید نوع آن کلاس را مشخص کنید یا از کلمه کلیدی var استفاده کنید:

B obj = new B(); 

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

A obj = new C(); 

دقت کنید که نوع متغیر obj را از نوع A در نظر گرفتیم، اما شی ای از نوع C داخل آن ریختیم. دلیل این امر آن است که کلاس C از کلاس A مشتق شده و به نوعی قابل تبدیل به کلاس A می باشد. اما به این نکته توجه داشته باشید، زمانی که نوع متغیر را از نوع کلاس والد در نظر می گیریم

هنگام استفاده از شی ساخته شده، تنها خصوصیات و رفتارهایی هایی قابل استفاده هستند که در کلاس والد تعریف شده اند. به عنوان مثال، کد زیر صحیح نمی باشد، به این خاطر که Item3 داخل کلاس C تعریف شده و ما تنها به Item1 و Item2 که داخل A تعریف شده اند دسترسی داریم.

A instanceOfC = new C(); instanceOfC.Item3 = "C# Course"; 

یکی از مهمترین کاربردهای استفاده از نوع داده کلاس والد، مبحث Polymorphism می باشد که در بخش های بعدی با این مفهوم بیشتر آشنا خواهیم شد.

کلاس Object

در کتابخانه دات نت، کلاسی وجود دارد به نام Object یا شی. در دات نت، کلیه نوع های داده و کلاس ها، چه آنهایی که به صورت دستی می نویسیم و چه آنهایی که در کتابخانه دات نت وجود دارند، از کلاس object مشتق شده اند، به جز کلاس هایی که برای آنها کلاس والد را مشخص کرده ایم.

حتی کلاس هایی که برای آنها کلاس والد مشخص شده، باز هم شاخه اصلی زنجیره وراثت به کلاس object ختم می شود. پس به این صورت می گوییم که کلاس object، کلاس پایه ای برای کلیه کلاس های دات نت می باشد. یکسری رفتارها برای کلاس Object تعریف شده اند که در تمامی کلاس ها در دسترس هستند، زیرا کلیه کلاس ها از کلاس object مشتق شده اند. این رفتارها به شرح زیر می باشند:

  1. Equals: این رفتار بررسی می کند که دو شی با یکدیگر برابر هستند یا خیر.
  2. GetHashCode: این رفتار عددی را برمی گرداند که شناسه شی ایجاد شده می باشد.
  3. GetType: نوع یا Type شی را بر میگرداند. این متد را در بخش Reflection بیشتر بررسی خواهیم کرد.
  4. ToString: زمانی که این رفتار را برای یک شی صدا می زنید، رشته ای مرتبط با آن شی را بر میگرداند که به صورت پیش فرض Type Name یا نام نوع آن کلاس را بر میگرداند. در بخش Polymorphism با این متد بیشتر آشنا می شوید.

گفتیم زمان تعریف کردن یک شی، نوع داده والد را به جای خود کلاس برای متغیر در نظر گرفت:

object number = 12; object name = "Hossein Ahmadi"; object instance = new A(); 

همینطور که مشاهده می کنید فرقی نمی کند مقدار متغیر چه باشد، هر مقداری را می توان در متغیر از نوع object ذخیره کرد. در این قسمت سعی کردیم مقدماتی از مبحث وراثت را با هم مرور کنیم. در بخش بعدی بحث وراثت را با بررسی مفهوم Polymorphism ادامه خواهیم داد.

Polymorphism

همانطور که در بخش قبل گفتیم وراثت یکی از اصلی ترین مباحث برنامه نویسی شی گرا می باشد. در بخش قبلی با شیوه ارث بری از کلاس ها آشنا شدیم. یکی از مفاهیمی که در برنامه نویسی شی گرا خیلی کاربرد دارد و وابسته به مفهوم وراثت است، چند ریختی یا Polymorphism است.

در قسمت مقدمه برنامه نویسی شی گرا با تعریف کلی Polymorphism آشنا شدیم و در این بخش تصمیم داریم به صورت عملی با نحوه پیاده سازی این قابلیت در زبان سی شارپ آشنا شویم.همانطور که در قسمت مقدمه گفتیم، Polymorphism به معنای قابلیت تعریف مجدد رفتار یک موجودیت در کلاس های فرزند می باشد. Polymorphism در زبان سی شارپ به سه روش قابل پیاده سازی است:

  1. استفاده از متد های virtual و override کردن آنها در کلاس های فرزند
  2. استفاده از رفتارهای abstract در کلاس والد
  3. استفاده از قابلیت interface ها

در این قسمت، حالت اول را بررسی می کنیم و حالت دوم و سوم، یعنی استفاده از متدهای abstract و interface ها را در بخش های بعدی توضیح خواهیم داد.

متدهای virtual

همانطور که گفتیم یکی از روش های پیاده سازی Polymorphism استفاده از متدهای virtual و override کردن آنها در کلاس فرزند است. برای مثال، فرض کنیم کلاس پایه ای داریم با عنوان Shape که در آن رفتاری با نام Draw تعریف کردیم. رفتار Draw وظیفه ترسیم شی را بر عهده دارد.

در این مثال ها، تنها در متدها پیامی را در پنجره کنسول چاپ می کنیم، اما در محیط واقعی هر یک از این متدها وظیفه ترسیم شی را بر عهده خواهند داشت. همانطور که گفتیم کلاس Shape رفتار Draw را تعریف می کند. این رفتار در بین تمامی اشیاء ای که از کلاس Shape مشتق می شوند مشترک است. در ابتدا کلاس Shape را به صورت زیر تعریف می کنیم:

public class Shape {     public void Draw()     {         Console.WriteLine("Drawing the shape!");     } } 

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

public class Rectangle : Shape {          }  public class Triangle : Shape {          }  public class Circle : Shape {          } 

هر سه کلاسی که در بالا تعریف کردیم، شامل متد Draw هستند، زیرا این متد در کلاس پایه یعنی 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! 

اما خروجی مدنظر ما تولید نشده است. ما می خواهیم هر کلاس رفتار مربوط به خود را داشته باشد. درست است که رفتار Draw در کلاس پایه تعریف شده، اما باید بتوانیم این رفتار را برای کلاس های فرزند تغییر دهیم. برای اینکار باید در کلاس پایه مشخص کنیم که کدم رفتار را می خواهیم تغییر دهیم. برای اینکار، کافیست رفتار مورد نظر را از نوع virtual تعریف کنیم. اعضای virtual به ما این اجازه را می دهند تا در کلاس فرزند مجدد آنها را تعریف کنیم. برای اینکار متد Draw در کلاس Shape را به صورت زیر تغییر می دهیم:

public virtual void Draw() {     Console.WriteLine("Drawing the shape!"); } 

دقت کنید، کلمه کلیدی virtual قبل از نوع بازگشتی متد نوشته می شود. حالا باید در کلاس فرزند رفتار Draw را مجدداً تعریف کنیم. برای اینکار باید متدی که از نوع virtual تعریف شده است را override کنیم. ابتدا رفتار Draw را برای کلاس Rectangle تغییر می دهیم. کد کلاس Rectangle را به صورت زیر تغییر دهید:

public class Rectangle : Shape {     public override void Draw()     {         Console.WriteLine("Drawing rectangle!");     } } 

حالا با اجرای مجدد کد خروجی به صورت زیر تغییر می کند:

Drawing rectangle! Drawing the shape! Drawing the shape! 

زمانی که شما داخل کلاس Rectangle شروع به تایپ می کنید، بعد از نوشتن کلمه کلیدی override و زدن کلید space لیستی از متدهایی که قابل override شدن هستند برای شما نمایش داده می شوند:

 

img1

همچنین در صورتی که ابزار Resharper را نصب کرده باشید، وارد scope کلاس شده و کلید های Alt+Insert را فشار دهید، با اینکار منوی Generate برای شما نمایش داده می شود، از طریق این منو و انتخاب گزینه Overriding members لیستی از تمامی اعضای قابل override شدن به شما نمایش داده می شود:

 

img2

 

img3

بعد از انتخاب عضو مورد نظر و فشار دادن کلید Finish متد مورد نظر برای شما override می شود. مهمترین کاربرد این ویژگی، زمانی است که شما تصمیم دارید چندین ویژگی را با هم override کنید. به یک نکته توجه داشته باشید، چه با روش اول متد را override کنید، چه با روش دوم، کدی برای شما به صورت خودکار درج می شود که به صورت زیر است:

public override void Draw() {     base.Draw(); } 

در قسمت قبل با کلمه کلیدی base آشنا شدید، در کد بالا که به صورت پیش فرض نوشته می شود، با فراخوانی متد Draw از شی ای که متد داخل آن override شده، نسخه کلاس پایه از رفتار فراخوانی می شود که شما باید بر اساس نیاز خود کد مورد نظر را برای رفتار override شده بنویسید.کلاس های Triangle و Circle را نیز به صورت زیر تغییر می دهیم:

public class Triangle : Shape {     public override void Draw()     {         Console.WriteLine("Draw triangle!");     }         }  public class Circle : Shape {     public override void Draw()     {         Console.WriteLine("Draw circle!");     }         } 

بعد از اجرای برنامه، خروجی باید به صورت زیر باشد:

Drawing rectangle! Drawing triangle! Drawing circle! 

شاید خیلی از دوستان این سوال برایشان پیش بیاید که دلیل اینکار چیست؟ ما که متدها را در هر کلاس نوشتیم، چرا باید از کلاس پایه و override کردن آنها استفاده می کردیم؟ مهمترین خاصیت استفاده از Polymorphism استفاده از کلاس پدر برای کارهاست.

برای درک بهتر، فرض کنید میخواهیم آرایه ای از اشیاء داشته باشیم. همانطور که می دانید، ما سه کلاس مختلف داریم و در صورت استفاده نکردن از قابلیت وراثت باید برای هر یک از کلاس ها یک آرایه تعریف کنیم. اما با قابلیت وراثت می توان یک آرایه از نوع کلاس پایه تعریف کرد و اشیاء فرزند را داخل آن قرار داد:

Shape[] shapes = new Shape[5]; shapes[0] = new Circle(); shapes[1] = new Triangle(); shapes[2] = new Circle(); shapes[3] = new Rectangle(); shapes[4] = new Triangle();  foreach (var shape in shapes)     shape.Draw();  Console.ReadKey(); 

با اجرای کد بالا خروجی زیر به نمایش داده می شود:

Drawing circle! Drawing triangle! Drawing circle! Drawing rectangle! Drawing triangle! 

با اینکه ما کلاس پایه یعنی Shape را به عنوان نوع آرایه در نظر گرفتیم، اما در هر خانه از آرایه ای شی ای از نوع فرزندان کلاس Shape قرار دادیم و به دلیل override کردن رفتار Draw در کلاس های مشتق شده، فراخوانی متد Draw بر اساس تعریفی که در کلاس های فرزند داشتیم انجام می شود.

تعریف مجدد یا override کردن تنها محدود به رفتارها یا همان متدها نمی باشد. شما خصوصیات یا Property ها را نیز می توانید از نوع virtual تعریف کنید. برای مثال کلاسی را با نام Human فرض کنید که دارای سه خصوصیت به نام های FirstName و LastName و FullSpecification می باشد:

public class Human {     public string FirstName { get; set; }     public string LastName { get; set; }      public string FullSpecification     {         get { return FirstName + " " + LastName; }     } } 

در کلاس بالا، خاصیت FullSpecification نام کامل را بر میگرداند. حالا کلاس فرزندی تعریف می کنیم با نام Employee یا کارمند که از کلاس Human مشتق شده است و خاصیت جدید با نام JobPosition یا موقعیت شغلی به آن اضافه می کنیم:

public class Employee : Human {     public string JobPosition { get; set; } } 

در صورتی که شی ای از روی Employee بسازیم و خاصیت FullSpecification آن را چاپ کنیم، نام و نام خانوداگی او در خروجی چاپ می ش