دسته بندی وبلاگ

Indexers ایندکسرها در سی شارپ


در اين درس با انديكسرها در #C آشنا مي‌شويم. اهداف اين درس به شرح زير مي‌باشند :
• پياده‌سازي ایندكسرها
• سرريزي ایندكسرها (Overload)
• درك چگونگي پياده‌سازي ایندكسرهای چندپارامتری
• خلاصه
• نكات مهم و مطالب كمكي در زمينه ایندكسرها 


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

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

كد:

using System;
/// <summary>
/// مثالي ساده از يك انديكسر
/// </summary>
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("nIndexer Outputn");
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;
/// <summary>
/// پياده‌سازي انديكسرهاي سرريز شده
/// </summary>
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("nIndexer Outputn");
for (int i=0; i < size; i++)
{
Console.WriteLine("myInd[{0}]: {1}", i, myInd[i]);
}//end of for
Console.WriteLine("nNumber 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) انديكسرها رخ مي‌دهد. در هنگام فراخواني انديكسرها، كلاس تنها از روي نوع بازگشتي انديكسر و تعداد پارامترهاي آن متوجه مي‌شود كه منظور فراخواننده استفاده از كدام انديسكر بوده است.

2. از انديكسر چگونه مانند آرايه استفاده مي‌شود؟
همانطور كه در اين درس مشاهده كرديد دسترسي به عناصر انديكسر همانند آرايه‌ها با استفاده از يك انديس صورت مي‌پذيرد. با استفاده از اين انديس مي‌توان به عنصر مورد نظر كلاس دسترسي پيدا نمود.

3. يك مثال عملي استفاده از انديكسرها چيست؟
يك نمونة بسيار جالب از استفادة انديكسرها كنترل 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<lbt.GetNumEntries( );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# است تا بعد از اين آشنايي به طور كامل و بسيار پيشرفته به بررسي كليه مفاهيم زبان بپردازيم. پس از اتمام آموزش اوليه تحولات اساسي در سايت مشاهده خواهيد كرد و در آن هنگام به بررسي كامل هر مبحث با مثال‌هايي بسيار واقعي و كاربردي خواهيم پرداخت.