درس یازدهم – اندیکسرها در C# (Indexers)

درس یازدهم – اندیکسرها در C# (Indexers)

 

در این درس با اندیکسرها در C# آشنا می‌شویم. اهداف این درس به شرح زیر می‌باشند :

  • پیاده‌سازی اندیکسر
  • سرریزی اندیکسرها (Overload)
  • درک چگونگی پیاده‌سازی اندیکسرهای چند پارامتری
  • خلاصه
  • نکات مهم و مطالب کمکی در زمینه اندیکسرها

 

اندیکسرها

اندیکسرها مفهومی بسیار ساده در زبان C# هستند. با استفاده از آنها می‌توانید از کلاس خود همانند یک آرایه استفاده کنید. در داخل کلاس مجموعه‌ای از مقادیر را به هر طریقی که مورد نظرتان هست مدیریت کنید. این اشیاؤ می‌توانند شامل مجموعه‌ای از اعضای کلاس، یک آرایه دیگر، و یا مجموعه‌ای از ساختارهای پیچیده داده‌ای باشند، جدا از پیاده‌سازی داخلی کلاس، داده‌های این ساختارها از طریق استفاده از اندیکسرها قابل دسترسی هستند. به مثالی در این زمینه توجه کنید :

 

مثال 11-1 : نمونه‌ای از یک اندیکسر

using System;

///

/// مثالی ساده از یک اندیکسر

///

class IntIndexer

{

private string[] myData;

public IntIndexer(int size)

{

myData = new string[size];

for (int i=0; i < size; i++)

{

myData[i] = "empty";

}

}

public string this[int pos]

{

get

{

return myData[pos];

}

set

{

myData[pos] = value;

}

}

static void Main(string[] args)

{

int size = 10;

IntIndexer myInd = new IntIndexer(size);

myInd[9] = "Some Value";

myInd[3] = "Another Value";

myInd[5] = "Any Value";

Console.WriteLine(" Indexer Output ");

for (int i=0; i < size; i++)

{

Console.WriteLine("myInd[{0}]: {1}", i, myInd[i]);

}

}

}

مثال 11-1 نحوه پیاده‌سازی اندیکسر را نشان می‌دهد. کلاس IntIndexer دارای آرایة رشته‌ای بنام myData می‌باشد. این آرایه، عنصری خصوصی (private) است و کاربران خارجی (external users) نمی‌توانند به آن دسترسی داشته باشند. این آرایه درون سازندة (constructor) کلاس تخصیص‌دهی می‌گردد که در آن پارامتر size از نوع int دریافت می‌شود، از آرایه myData نمونه‌ای جدید ایجاد می‌گردد، سپس هر یک از المانهای آن با کلمه "empty" مقدار‌دهی می‌گردد.

 عضو بعدی کلاس، اندیکسر است که بوسیلة کلمه کلیدی this و دو براکت تعریف شده است، this[int pos]. این اندیکسر پارامتر موقعیتی pos را دریافت می‌نماید. همانطور که حتماً تا کنون دریافته‌اید پیاده‌سازی اندیکسر بسیار شبیه به پیاده‌سازی یک ویژگی (property) است. اندیکسر نیز دارای accessor های set و get است که دقیقاً همانند property عمل می‌کنند. همانطور که در اعلان این اندیکسر نیز مشاهده می‌شود، متغیری از نوع رشته‌ای را باز می‌گرداند.

 در متد Main() شیء جدیدی از IntIndexer ایجاد شده است و مقادیری به آن افزوده می‌شود و سپس نتایج چاپ می‌گردند. خروجی این برنامه به شکل زیر است :

Indexer Output

myInd[0]: empty

myInd[1]: empty

myInd[2]: empty

myInd[3]: Another Value

myInd[4]: empty

myInd[5]: Any Value

myInd[6]: empty

myInd[7]: empty

myInd[8]: empty

myInd[9]: Some Value

 

استفاده از integer جهت دسترسی به آرایه‌ها در اغلب زبانهای برنامه‌سازی رایج است ولی زبان  C# چیزی فراتر از آنرا نیز پشتیبانی می‌کند. در C# اندیکسرها را می‌توان با چندین پارامتر تعریف کرد و هر پارامتر می‌تواند از نوع خاصی باشد. پارامتر‌های مختلف بوسیلة کاما از یکدیگر جدا می‌شوند. پارامترهای مجاز برای اندیکسر عبارتند از : integer، enum و string. علاوه بر آن، اندیکسرها قابل سرریزی (Overload) هستند. در مثال 2-11 تغییراتی در مثال قبل ایجاد کرده‌ایم تا برنامه قابلیت دریافت اندیکسرهای سرریز شده را نیز داشته باشد.

 سرریزی اندیکسرها

مثال 2-11 : اندیکسرهای سرریز شده (Overloaded Indexers)

using System;

///

/// پیاده‌سازی اندیکسرهای سرریز شده

///

class OvrIndexer

{

private string[] myData;

private int arrSize;

 

public OvrIndexer(int size)

{

arrSize = size;

myData = new string[size];

for (int i=0; i < size; i++)

{

myData[i] = "empty";

}//end of for

}//end of constructor

 

public string this[int pos]

{

get

{

return myData[pos];

}

set

{

myData[pos] = value;

}

}//end of indexer

 

public string this[string data]

{

get

{

int count = 0;

for (int i=0; i < arrSize; i++)

{

if (myData[i] == data)

{

count++;

}//end of if

}//end of for

return count.ToString();

}//end of get

set

{

for (int i=0; i < arrSize; i++)

{

if (myData[i] == data)

{

myData[i] = value;

}//end of if

}//end of for

}//end of set

}//end of overloaded indexer

 

static void Main(string[] args)

{

int size = 10;

 

OvrIndexer myInd = new OvrIndexer(size);

myInd[9] = "Some Value";

myInd[3] = "Another Value";

myInd[5] = "Any Value";

myInd["empty"] = "no value";

Console.WriteLine(" Indexer Output ");

for (int i=0; i < size; i++)

{

Console.WriteLine("myInd[{0}]: {1}", i, myInd[i]);

}//end of for

Console.WriteLine(" Number of "no value" entries: {0}", myInd["no value"]);

}//end of Main()

}//end of class

 

 مثال 2-11 نحوه سرریز کردن اندیکسر را نشان می‌دهد. اولین اندیکسر که دارای پارامتری از نوع int تحت عنوان pos است دقیقاً مشابه مثال 1-11 است ولی در اینجا اندیکسر جدیدی نیز وجود دارد که پارامتری از نوع string دریافت می‌کند. get accessor اندیکسر جدید رشته‌ای را برمی‌گرداند که نمایشی از تعداد آیتمهایی است که با پارامتر مقداری data مطابقت می‌کند. set accessor مقدار هر یک از مقادیر ورودی آرایه را که مقدارش با پارامتر data مطابقت نماید را به مقداری که به اندیکسر تخصیص داده می‌شود، تغییر می‌دهد.

 رفتار (behavior) اندیکسر سرریز شده که پارامتری از نوع string دریافت می‌کند، در متد Main() نشان داده شده است. در اینجا set accessor مقدار "No value" را به تمام اعضای کلاس myInd که مقدارشان برابر با "empty" بوده است، تخصیص می‌دهد. این accessor از دستور زیر استفاده نموده است : myInd["empty"] = "No value" . پس از اینکه تمامی اعضای کلاس myInd چاپ شدند، تعداد اعضایی که حاوی "No value" بوده‌اند نیز نمایش داده می‌شوند. این امر با استفاده از دستور زیر در get accessor روی می‌دهد : myInd["No value"]. خروجی برنامه بشکل زیر است :

Indexer Output

myInd[0]: no value

myInd[1]: no value

myInd[2]: no value

myInd[3]: Another Value

myInd[4]: no value

myInd[5]: Any Value

myInd[6]: no value

myInd[7]: no value

myInd[8]: no value

myInd[9]: Some Value

Number of "no value" entries: 7

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

public object this[int param1, ..., int paramN]

{

get

{

// process and return some class data

}

set

{

// process and assign some class data

}

}

خلاصه :

هم اکنون شما با اندیکسرها و نحوة پیاده‌سازی آنها آشنا شده‌اید. با استفاده از اندیکسرها می‌توان به عناصر یک کلاس همانند یک آرایه دسترسی پیدا کرد. در این مبحث اندیکسرهای سرریز شده و چند پارامتری نیز مورد بررسی قرار گرفتند.

در آینده و در مباحث پیشرفته‌تر با موارد بیشتری از استفادة اندیکسرها آشنا خواهید شد.

 نکات :

  1. منظور از اندیکسر سرریز شده چیست؟

 هنگامیکه از دو یا چند اندیکسر درون یک کلاس استفاده می‌کنیم، سرریزی (Overloading) اندیکسرها رخ می‌دهد. در هنگام فراخوانی اندیکسرها، کلاس تنها از روی نوع بازگشتی اندیکسر و تعداد پارامترهای آن متوجه می‌شود که منظور فراخواننده استفاده از کدام اندیسکر بوده است.

  1. از اندیکسر چگونه مانند آرایه استفاده می‌شود؟

همانطور که در این درس مشاهده کردید دسترسی به عناصر اندیکسر همانند آرایه‌ها با استفاده از یک اندیس صورت می‌پذیرد. با استفاده از این اندیس می‌توان به عنصر مورد نظر کلاس دسترسی پیدا نمود.

  1. یک مثال عملی  استفاده از  اندیکسرها چیست؟

یک نمونة بسیار جالب از استفادة اندیکسرها کنترل ListBox است. (ListBox عنصری است کنترلی که با استفاده از آن لیستی از عناصر رشته‌ای نمایش داده می‌شوند و کاربر با انتخاب یکی از این گزینه‌ها با برنامه ارتباط برقرار می‌کند. در حقیقت این عنصر کنترلی یکی از روشهای دریافت اطلاعات از کاربر است با این تفاوت که در این روش ورودی‌هایی که کاربر می‌تواند وارد نماید محدود شده هستند و از قبل تعیین شده‌اند. نمونه‌ای از یک ListBox قسمت انتخاب نوع فونت در برنامة Word است که در آن لیستی از فونتهای موجود در سیستم نمایش داده می‌شود و کاربر با انتخاب یکی از آنها به برنامه اعلام می‌کند که قصد استفاده از کدام فونت سیستم را دارد.) ListBox نمایشی از ساختمان داده ایست شبیه به آرایه که اعضای آن همگی از نوع string هستند. علاوه بر این این کنترل می‌خواهد تا در هنگام انتخاب یکی از گزینه‌هایش بتواند اطلاعات خود را بطور خودکار update نماید و یا به عبارتی بتواند ورودی دریافت نماید. تمامی این اهداف با استفاده از اندیکسر میسر می‌شود. اندیکسرها شبیه به property ها اعلان می‌شوند با این تفاوت مهم که اندیکسرها بدون نام هستند و نام آنها تنها کلمه کلیدی this است و همین this مورد اندیکس شدن قرار می‌گیرد و سایر موارد بشکل پارامتر به اندیکسر داده می‌شوند.

public class ListBox: Control

{

private string[] items;

 

public string this[int index]

{

get

{

return items[index];

}

set

{

items[index] = value;

Repaint();

}

}

}

با نگاه به نحوه استفاده از اندیکسر بهتر می‌توان با مفهوم آن آشنا شد. برای مثال دسترسی به ListBox بشکل زیر است :

ListBox listBox = ...;

listBox[0] = "hello";

Console.WriteLine(listBox[0]);

 

نمونه برنامه‌ای که در آن نحوة استفاده از اندیکسر در عنصر کنترلی ListBox نشان داده شده، در زیر آورده شده است :

 

Csharp-Persian_Indexer_Demo

 

using System;

 

public class ListBoxTest

{

 // تخصیص داده می‌شوند.ListBoxرشته‌های مورد نظر به

public ListBoxTest(params string[] initialStrings)

{

 // فضایی را برای ذخیره‌سازی رشته‌های تخصیص می‌دهد.

strings = new String[256];

 // رشته‌های وارد شده به سازنده را درون آرایه‌ای کپی می‌کند.

foreach (string s in initialStrings)

{

strings[ctr++] = s;

}

}//end of constructor

 

 // رشته‌ای به انتهای کنترل افزوده می‌شود.

public void Add(string theString)

{

if (ctr >= strings.Length)

{

 // در این قسمت می‌توان کدی جهت کنترل پر شدن فضای تخصیص داده شده قرار داد.

}

else

strings[ctr++] = theString;

}//end of Add()

 

 // اعلان اندیکسر

public string this[int index]

{

get

{

if (index < 0 || index >= strings.Length)

{

// در این قسمت می‌توان کدی جهت کنترل پر شدن فضای تخصیص داده شده قرار داد.

}

return strings[index];

}//end of get

set

{

if (index >= ctr )

{

 // فراخوانی متدی جهت کنترل خطا

}

else

strings[index] = value;

}//end of set

}//end of indexer

 

// تعداد رشته‌های موجود را نشان می‌دهد

public int GetNumEntries( )

{

return ctr;

}

 

private string[] strings;

private int ctr = 0;

}//end of ListBoxTest class

 

public class Tester

{

static void Main( )

{

 //جدید و تخصیص دهی آن ListBox ساخت یک

ListBoxTest lbt = new ListBoxTest("Hello", "World");

 // رشته‌های مورد نظر به کنترل افزوده می‌شوند.

lbt.Add("Who");

lbt.Add("Is");

lbt.Add("John");

lbt.Add("Galt");

 // رشتة جدیدی در خانه شمارة یک فرار داده می‌شود.

string subst = "Universe";

lbt[1] = subst;

 // کلیه آیتمهای موجود نمایش داده می‌شوند.

for (int i = 0;i

{

Console.WriteLine("lbt[{0}]: {1}",i,lbt[i]);

}

}//end of Main()

}//end of Tester class

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

 

Output:

 

lbt[0]: Hello

lbt[1]: Universe

lbt[2]: Who

lbt[3]: Is

lbt[4]: John

lbt[5]: Galt

 

توجه :

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

درس دهم – ویژگیها در C#

درس دهم – ویژگیها در C#

   

در این درس با ویژگیها (Properties) در زبان C# آشنا خواهیم شد. اهداف این درس به شرح زیر می‌باشد :

  • موارد استفاده از Property ها
  • پیاده‌سازی Property
  • ایجاد Property فقط خواندنی (Read-Only)
  • ایجاد Property فقط نوشتنی (Write-Only)

 

Property ها امکان ایجاد حفاظت از فیلدهای یک کلاس را از طریق خواندن و نوشتن بوسیله Property را فراهم می‌نماید. Property ها علاوه بر اینکه از فیلدهای یک کلاس حفاظت می‌کنند، همانند یک فیلد قابل دسترسی هستند. بمنظور درک ارزش Property ها بهتر است ابتدا به روش کلاسیک کپسوله کردن متدها توجه نمایید.

 

مثال 1-10 : یک نمونه از چگونگی دسترسی به فیلدهای کلاس به طریقه کلاسیک

using System;

public class PropertyHolder

{

private int someProperty = 0;

public int getSomeProperty()

{

return someProperty;

}

public void setSomeProperty(int propValue)

{

someProperty = propValue;

}

}

public class PropertyTester

{

public static int Main(string[] args)

{

PropertyHolder propHold = new PropertyHolder();

propHold.setSomeProperty(5);

Console.WriteLine("Property Value: {0}", propHold.getSomeProperty());

return 0;

}

}

مثال 1-10 روش کلاسیک دسترسی به فیلدهای یک کلاس را نشان می‌دهد. کلاس PropertyHolder دارای فیلدی است تمایل داریم به آن دسترسی داشته باشیم. این کلاس دارای دو متد getSomeProperty() و setSomePropery() می‌باشد. متد getSomeProperty() مقدار فیلد someProperty را باز می‌گرداند و متد setSomeProperty() مقداری را به فیلد someProperty تخصیص می‌دهد.

 

کلاس PropertyTester از متدهای کلاس PropertyHolder جهت دریافت مقدار فیلد someProperty از کلاس PropertyHolder استفاده می‌کند. در متد Main() نمونه جدیدی از شی PropertyHolder با نام propHold ایجاد می‌گردد. سپس بوسیله متد setSomeProperty، مقدار someMethod از propHold برابر با 5 می‌گردد و سپس برنامه مقدار property را با استفاده از فراخوانی متد Console.WriteLine() در خروجی نمایش می‌دهد. آرگومان مورد استفاده برای بدست آوردن مقدار property فراخوانی به متد getSomeProperty() است که توسط آن عبارت “Property Value : 5” در خروجی نمایش داده می‌شود.

 

چنین متد دسترسی به اطلاعات فیلد بسیار خوب است چرا که از نظریه کپسوله کردن شیء‌گرایی پشتیبانی می‌کند. اگر پیاده‌سازی someProperty نیز تغییر یابد و مثلا از حالت int به byte تغییر یابد، باز هم این متد کار خواهد کرد. حال همین مسئله با استفاده از خواص Property ها بسیار ساده‌تر پیاده‌سازی می‌گردد. به مثال زیر توجه نمایید.

 

مثال 2-10 : دسترسی به فیلدهای کلاس به استفاده از Property ها

using System;

public class PropertyHolder

{

private int someProperty = 0;

public int SomeProperty

{

get

{

return someProperty;

}

set

{

someProperty = value;

}

}

}

public class PropertyTester

{

public static int Main(string[] args)

{

PropertyHolder propHold = new PropertyHolder();

propHold.SomeProperty = 5;

Console.WriteLine("Property Value: {0}", propHold.SomeProperty);

return 0;

   }

}

 

مثال 2-10 چگونگی ایجاد و استفاده از ویژگیها (Property) را نشان می‌دهد. کلاس PropertyHolder دارای پیاده‌سازی از ویژگی SomeProperty است. توجه نمایید که اولید حرف از نام ویژگی با حرف بزرگ نوشته شده و این تنها تفاوت میان اسم ویژگی SomeProperty و فیلد someProperty می‌باشد. ویژگی دارای دو accessor با نامهای set و get است. accessor get مقدار  فیلد someProperty را باز می‌گرداند. set accessor نیز با استفاده از مقدار value، مقداری را به someProperty تخصیص می‌دهد. کلمه value که در set accessor آورده شده است جزو کلمات رزرو شده زبان C# می‌باشد.

 

کلاس PropertyTester از ویژگی someProperty مربوط به کلاس PropertyHolder استفاده می‌کند. اولین خط در متد Main() شی‌ای از نوع PropertyHolder با نام propHold ایجاد می‌نماید. سپس مقدار فیلد someProperty مربوط به شیء propHold، با استفاده از ویژگی SomeProperty به 5 تغییر می‌یابد و ملاحظه می‌نمایید که مسئله به همین سادگی است و تنها کافی است تا مقدار مورد نظر را به ویژگی تخصیص دهیم.

 

پس از آن، متد Console.WriteLine() مقدار فیلد someProperty شیء propHold را چاپ می‌نماید. این عمل با استفاده از ویژگی SomeProperty شیء propHold صورت می‌گیرد.

 

ویژگیها را می‌توان طوری ایجاد نمود که فقط خواندنی (Read-Only) باشند. برای این منظور تنها کافیست تا در ویژگی فقط از get accessor استفاده نماییم. به مثال زیر توجه نمایید.

 

ویژگیهای فقط خواندنی (Read-Only Properties)

مثال 3-10 : ویژگیهای فقط خواندنی

using System;

public class PropertyHolder

{

private int someProperty = 0;

public PropertyHolder(int propVal)

{

someProperty = propVal;

}

public int SomeProperty

{

get

{

return someProperty;

}

}

}

public class PropertyTester

{

public static int Main(string[] args)

{

PropertyHolder propHold = new PropertyHolder(5);

Console.WriteLine("Property Value: {0}", propHold.SomeProperty);

return 0;

}

}

 

مثال 3-10 چگونگی ایجاد یک ویژگی فقط خواندنی را نشان می‌دهد. کلاس PropertyHolder دارای ویژگی SomeProperty است که فقط get accessor را پیاده‌سازی می‌کند. این کلاس PropertyHolder دارای سازنده‌ایست که پارامتری از نوع int دریافت می‌نماید.

 

متد Main() از کلاس PropertyTester شیء جدیدی از PropertyHolder با نام propHold ایجاد می‌نماید. این نمونه از کلاس PropertyHolder از سازندة آن که مقداری صحیح را بعنوان پارامتر دریافت می‌کند، استفاده می‌کند. در این مثال این مقدار برابر با 5 در نظر گرفته می‌شود. این امر باعث تخصیص داده شدن عدد 5 به فیلد someProperty از شیء propHold می‌شود.

 

تا زمانیکه ویژگی SomeProperty از کلاس PropertyHolder فقط خواندنی است، هیچ راهی برای تغییر مقدار فیلد someProperty وجود ندارد. بعنوان مثال در صورتیکه عبارت propHold.SomeProperty = 7 را در کد برنامه اضافه نمایید، برنامة شما کامپایل نخواهد شد چراکه ویژگی SomeProperty فقط خواندنی است. اما اگر از این ویژگی در متد Console.WriteLine() استفاده نمایید بخوبی کار خواهد کرد زیرا این دستور تنها یک فرآیند خواندن است و با استفاده از get accessor این عمل قابل اجرا است.

 

ویژگیهای فقط نوشتنی (Write-Only Properties)

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

 

مثال 4-10 : ویژگیهای فقط خواندنی

using System;

public class PropertyHolder

{

private int someProperty = 0;

public int SomeProperty

{

set

{

someProperty = value;

Console.WriteLine("someProperty is equal to {0}", someProperty);

}

}

}

public class PropertyTester

{

public static int Main(string[] args)

{

PropertyHolder propHold = new PropertyHolder();

propHold.SomeProperty = 5;

return 0;

}

}

 

مثال 4-10 چگونگی ایجاد و استفاده از ویژگی فقط نوشتنی را نشان می‌دهد. در این حالت get accessor را از ویژگی SomeProperty حذف کرده و به جای آن set accessor را قرار داده‌ایم.

 

متد Main() کلاس PropertyTester شی‌ای جدید از همین کلاس با سازندة پیش فرض آن ایجاد می‌نماید. سپس با استفاده از ویژگی SomeProperty از شیء propHold، مقدار 5 را به فیلد someProperty مربوط به شیء propHold تخصیص می‌دهد. در این حالت set accessor مربوط به ویژگی SomeProperty فراخوانی شده و مقدار 5 را به فیلد  someProperty تخصیص می‌دهد و سپس عبارت someProperty is equal to 5”  “را در خروجی نمایش می‌دهد.

 

خلاصه

در این درس با ویژگیها آشنا شدید و نحوه استفاده از آنها را فرا گرفتید. روشهای کلاسیک کپسوله کردن از طریق استفاده از متدهای مجزا صورت می‌گرفت ولی با استفاده از ویژگیها (Property) می‌توان به اجزای یک شیء همانند یک فیلد دسترسی پیدا کرد. ویژگیها را می‌توان به صورت فقط خواندنی و یا فقط نوشتنی نیز ایجاد نمود. با استفاده از ویژگیها دسترسی مستقیم به فیلدهای مورد نظر از یک کلاس از بین رفته و این دسترسی تنها از طریق ویژگی مورد نظر امکان‌پذیر می‌گردد.

درس نهم _ چند ریختی (Polymorphism)

درس نهم _ چند ریختی (Polymorphism)

 

در این درس به بررسی چند ریختی در زبان ‍C# خواهیم پرداخت. اهداف این درس عبارتند از :

  • چند ریختی چیست؟

  • پیاده‌سازی متد مجازی (Virtual Method)

  • Override کردن متد مجازی

  • استفاده از چند ریختی در برنامه‌ها

 

یکی دیگر از مفاهیم پایه‌ای در شی‌گرایی، چند ریختی (Polymorphism) است. با استفاده از این ویژگی، می‌توان برای متد کلاس مشتق شده پیاده‌سازی متفاوتی از پیاده‌سازی متد کلاس پایه ایجاد نمود. این ویژگی در جایی مناسب است که می‌خواهید گروهی از اشیا‌ء را به یک آرایه تخصیص دهید و سپس از متد هر یک از آنها را استفاده کنید. این اشیاء الزاما نباید از یک نوع شی‌ء باشند. هرچند اگر این اشیاء بواسطه ارث‌بری به یکدیگر مرتبت باشند، می‌توان آنها را بعنوان انواع ارث‌بری شده به آرایه اضافه نمود. اگر هر یک از این اشیاء دارای متدی با نام مشترک باشند، آنگاه می‌توان هر یک از آنها را جداگانه پیاده‌سازی و استفاده نمود. در این درس با چگونگی انجام این عمل آشنا می‌گردید.

 

متد مجازی (Virtual Method)

using System;

 

public class DrawingObject

{

public virtual void Draw()

{

Console.WriteLine("I'm just a generic drawing object.");

}

}

مثال 1-9 کلاس DrawingObject را نشان می‌دهد. این کلاس می‌تواند بعنوان کلاسی پایه چهت کلاسهای دیگر در نظر گرفته شود. این کلاس تنها دارای یک متد با نام Draw() می‌باشد. این متد دارای پیشوند virtual است. وجود کلمه virtual بیان می‌دارد که کلاسهای مشتق شده از این کلاس می‌توانند، این متد را override نمایید و آنرا به طریقه دلخواه پیاده‌سازی کنند.

using System;

 

public class Line : DrawingObject

{

public override void Draw()

{

Console.WriteLine("I'm a Line.");

}

}

public class Circle : DrawingObject

{

public override void Draw()

{

Console.WriteLine("I'm a Circle.");

}

}

public class Square : DrawingObject

{

public override void Draw()

{

Console.WriteLine("I'm a Square.");

}

}

در مثال 2-9، سه کلاس دیده می‌شود. این کلاسها از کلاس DrawingObject ارث‌بری می‌کنند. هر یک از این کلاسها دارای متد Draw() هستند و تمامی آنها دارای پیشوند override می‌باشند. وجود کلمه کلیدی override قبل از نام متد، این امکان را فراهم می‌نماید تا کلاس، متد کلاس پایه‌ خود را override کرده و آنرا به طرز دلخواه پیاده‌سازی نماید. متدهای override شده باید دارای نوع و پارامترهای مشابه متد کلاس پایه باشند.

 

پیاده‌سازی چند ریختی

using System;

 

public class DrawDemo

{

public static int Main( )

{

DrawingObject[] dObj = new DrawingObject[4];

dObj[0] = new Line();

dObj[1] = new Circle();

dObj[2] = new Square();

dObj[3] = new DrawingObject();

foreach (DrawingObject drawObj in dObj)

{

drawObj.Draw();

}

return 0;

}

}

 

مثال 3-9 برنامه‌ای را نشان می‌دهد که از کلاسهای مثال 1-9 و 2-9 استفاده می‌کند. در این برنامه چند ریختی پیاده‌سازی شده است. در متد Main() یک آرایه ایجاد شده است. عناصر این آرایه از نوع DrawingObject تعریف شده است. این آرایه dObj نامگذاری شده و چهار عضو از نوع DrawingObject را در خود نگه می‌دارد.

 

سپس آرایه dObj تخصیص‌دهی شده است. به دلیل رابطه ارث‌بری این عناصر با کلاس DrawingObject، عناصر Line، Circle و Square قابل تخصیص به این آرایه می‌باشند. بدون استفاده از این قابلیت، قابلیت ارث‌بری، برای هر یک از این عناصر باید آرایه‌ای جدا می‌ساختید. ارث‌بری باعث می‌شود تا کلاسهای مشتق شده بتوانند همانند کلاس پایه خود عمل کنند که این قابلیت باعث صرفه‌جویی در وقت و هزینه تولید برنامه می‌گردد.

 

پس از تخصیص‌دهی آرایه، حلقه foreach تک تک عناصر آنرا پیمایش می کند. درون حلقه foreach متد Draw() برای هر یک از اعضای آرایه اجرا می‌شود. نوع شیء مرجع آرایه dObj، DrawingObject است. چون متد Draw() در هر یک از این اشیاء override می‌شوند، از اینرو متد Draw() مربوط به هر یک از این اشیاء اجرا می‌شوند. خروجی این برنامه بصورت زیر است :

I'm a Line.

I'm a Circle.

I'm a Square.

I'm just a generic drawing object.

متد override شده Draw() مربوط به هر یک از کلاسهای مشتق شده در برنامه فوق همانند خروجی اجرا می‌شوند. آخرین ط خروجی نیز مربوط به کلاس مجازی Draw() از کلاس DrawingObject است، زیرا آخرین عنصر آرایه شیء DrawingObject است.

 

خلاصه

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