شاخص‌ها (Indexers) در زبان C#

12 تیر 1396
درسنامه درس 14 از سری آموزش سی شارپ (C#)
csharp-indexers

شاخص‌‌ها به کلاس‌های اجازه می‌دهند که به صورت یک آرایه‌ مورد استفاده قرار بگیرند! شاید این جمله برای شما تعجب بر انگیز باشد. در فصل گذشته در ارتباط با ویژگی‌ها صحبت کردیم و انواع آنها را مورد بررسی قرار دادیم. در این فصل قصد داریم شاخص‌ها یا Indexer ها را در زبان برنامه‌نویسی #C مورد تجزیه و تحلیل قرار دهیم.

مقدمه

ناخودآگاه هر وقت صحبت از کلمه‌ی Index می‌شود ذهن ما به سمت آرایه‌ها می‌رود. به عبارت دیگر آرایه‌ها از ساده‌ترین اشیاء‌ای هستند که مفهوم Index در آنها وجود دارد. برای آگاهی بیشتر در ارتباط با آرایه‌ها در زبان برنامه‌نویسی #C مقاله‌ی زیر را مطالعه بفرمایید:

همانطور که در جریان هستید آرایه‌ها در زبان برنامه‌نویسی #C با علامت [] یا براکت شناخته می‌شوند.

تعریف شاخص‌ یا Indexer

شاخص یا indexer به نوع خاصی ویژگی یا Property در زبان برنامه‌نویسی #C گفته می‌شود که در بدنه‌ی اصلی کلاس تعریف شده و امکان استفاده از عملگر [] را برای نمونه‌ی کلاس فراهم می‌کند. به عبارت دیگر indexer قابلیتی را فراهم می‌کند که یک شیء مانند یک آرایه ایندکس شود. نحوه‌ی تعریف indexer‌ها به صورت زیر است:

public type this [type identifier]
{
     get{ ... }
     set{ ... }
}

در اجرای indexer و تعریف آن توجه به نکات زیر ضروری‌ست:

  • indexerها می‌توانند مانند آرایه‌های دو بعدی دارای چندپارامتری باشند (Multiple Parameters)
  • indexerها قابلیت overloaded‌ دارند
  • indexerها علاوه بر مقادیر صحیح عددی، مقادیر رشته یا double را پیاده‌سازی می‌کنند.
  • از accessor یا دستیاب get برای بازگرداندن مقدار و از set برای تنظیم کردن و مقداردهی استفاده می‌شود.
  • indexerها بر خلاف propertyها نامی ندارند و برای تعریف آنها کافی‌ست از کلمه‌ی کلیدی this استفاده کرد.

همانطور که ملاحظه می‌کنید تعریف indexer همانند ویژگی‌ها یا property‌ها است با این تفاوت که در accessor‌های موجود در ویژگی‌ها تنها یک مقدار مشخصی داده return یا بازگردانده می‌شود در حالیکه در accessorهای indexer یک مقدار خاص از یک شیء بازگردانده می‌شود. به عبارت دیگر indexer داده‌های یک نمونه را به قسمت‌های کوچکتر تقسیم کرده و سپس هر قسمت ایندکس شده را get یا set می‌کند.

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

using System;

/// <summary>
///     A simple indexer example.
/// </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 Output\n");

        for (int i=0; i < size; i++)
        {
            Console.WriteLine("myInd[{0}]: {1}", i, myInd[i]);
        }
    }
}

در این مثال نحوه‌ی پیاده‌سازی یک indexer را مشاهده می‌کنید. ابتدا کلاسی تحت عنوان IntIndexer ایجاد کرده‌ایم که دارای یک ویژگی از نوع آرایه‌ی رشته‌ای تحت عنوان myData است. این آرایه در سازنده پیش‌فرض کلاس مقدار‌دهی شده است که به ازای سایز و اندازه‌ی آرایه‌ی myData عبارت empty را درون آنها می‌ریزد. سپس در قسمت بعدی کلاس یک indexer با کلمه‌ی کلیدی this و [] براکت ایجاد شده است. این indexer یک موقعیت را به عنوان پارامتر int pos دریافت می‌کند. سپس با استفاده از دستیاب‌ها یا accessorهای get و set اقدام به فراخوانی یا مقداردهی مجموعه‌ی آرایه‌ی myData می‌کند. در متد اصلی Main نیز ابتدا یک سایر و اندازه را تعریف کرده و سپس یک نمونه از کلاس IntIndexer ایجاد می‌کنیم که مقدار پیش‌فرض را برای سازنده تنظیم و روی متغییر size قرار می‌دهیم. در نهایت در سه خط مقداردهی آرایه‌ی موجود در شیء را انجام داده و خروجی را برای تمام المان‌های موجود در شیء چاپ می‌کنیم. در نتیجه خروجی این مثال به صورت زیر خواهد بود:

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

استفاده مجدد یا سربارگیری (Overloaded) در Indexerها

همانطور که در فصول گذشته توضیح دادیم می‌توان یک متد و یا تابع را برای چندین نوع متغییر (مثلا int و string) تعریف کرد. بنابراین برای اینکار کافیست آرگومان ورودی به آن متد و تابع را از نوع مختلفی تعریف کرده و خروجی نهایی را با مقدار مشخص آن نوع بازگردانیم. در indexer ها یا شاخص‌ها نیز همین روند ادامه خواهد داشت. به مثال زیر توجه کنید:

using System;

/// <summary>
///     Implements overloaded indexers.
/// </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";
        }
    }

    public string this[int pos]
    {
        get
       {
            return myData[pos];
        }
        set
       {
            myData[pos] = value;
        }
    }

    public string this[string data]
    {
        get
       {
            int count = 0;

            for (int i=0; i < arrSize; i++)
            {
                if (myData[i] == data)
                {
                    count++;
                }
            }
            return count.ToString();
        }
        set
       {
            for (int i=0; i < arrSize; i++)
            {
                if (myData[i] == data)
                {
                    myData[i] = value;
                }
            }
        }
    }

    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 Output\n");

        for (int i=0; i < size; i++)
        {
            Console.WriteLine("myInd[{0}]: {1}", i, myInd[i]);
        }

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

همانطور که ملاحظه می‌کنید تمام روند اجرایی این مثال مشابه مثال قبل می‌باشد با این تفاوت که یک indexer دیگر با آرگومان ورودی string data از نوع string به مجموعه کلاس اضافه شده است. سپس داخل این indexer تعداد مقادیری که برابر no value هستند را شماره می‌کند. بنابراین در متد اصلی Main وقتی عبارت myData["no value" را در اختیار می‌گیریم در واقع شمارش را برای تعداد مقادیری که برابر با 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

همچنین می‌توان به صورت همزمان به Indexer ها چندین پارامتر اختصاص داد که در مجموعه‌ی دستورهای زیر مشاهده می‌کنید:

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

       {
            // process and return some class data
        }
        set

       {
            // process and assign some class data
        }
    }

در این بخش به توضیح کامل و دقیق شاخص‌ها یا indexer ها پرداختیم تا شما عزیزان با این ویژگی خاص در زبان برنامه‌نویسی #C بیشتر آشنا شوید. در فصل بعدی به توضیح مفصل struct ها پرداخته و مثال‌هایی کاربردی را ارائه خواهیم داد. با ما همراه باشید.

تمام فصل‌های سری ترتیبی که روکسو برای مطالعه‌ی دروس سری آموزش سی شارپ (C#) توصیه می‌کند:
نویسنده شوید

دیدگاه‌های شما

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.