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

overload کردن عملگرها در سی شارپ

مطالبی که در این قسمت مورد بررسی قرار خواهند گرفت به شرح زیر می باشند :

Overload کردن عملگرها چیست ؟
درک اینکه چه زمانی از Overload کردن عملگرها استفاده می کنیم.
چگونگی Overload کردن عملگرها
قوانین معمول در Overload کردن عملگرها

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

نگاهی بر Overload کردن عملگرها
همانطور که می دانید، در C# همانند سایر زبانهای برنامه سازی، عملگرهای متعددی وجود دارند (برای بررسی عملگرها می توانید به "عملگرها در C# " مراجعه نمایید.). این عملگرها برای انواع از پیش تعریف شده در زبان C# مورد استفاده قرار می گیرند. اما در موارد مورد نیاز می توان برای انواع تعریف شده توسط کاربر نیز، عملگرهای خاص مورد نظر را تعریف نمود. با استفاده از Overload کردن عملگرها می توان از عملگرهای تعریف شده نیز همانند عملگرهای موجود در زبان استفاده کرد.

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

برای این منظور، یک نوع جدید با نام Matrix اعلان می کنید که این نوع جدید می تواند یک کلاس و یا یک struct باشد. (برای بررسی کلاسها به " کلاسها در C# " و برای بررسی ساختارها به " ساختارها در C# " رجوع نمایید.) حال که نوع جدیدی با عنوان Matrix را اعلان نموده اید، مسلما می خواهید از روی آن نمونه هایی تولید کرده و عملیات ریاضی نظیر جمع و ضرب را بر روی آنها اجرا نمایید. برای انجام چنین عملیاتی می توان دو متد Add() و Product() را پیاده سازی نمود و از آنها استفاده نمود. مثلا شکل استفاده از این متدها بسته به نحوه آنها می تواند به یکی از شکلهای زیر باشد :

كد:

Matrix result = mat1.Add(mat2);  // instance
 
Matrix result = Matrix.Add(mat1, mat2);  // static
 
Matrix result = mat1.DotProduct(mat2).DotProduct(mat3); // and so on...



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

كد:

Matrix result = mat1 + mat2;
 
Matrix result = mat1 * mat2;
 
Matrix result = mat1 * mat2 * mat3 * mat4;



همانطور که ملاحظه می کنید، استفاده از چنین فرمتی به مراتب آسانتر از تعریف متدهایی برای اجرای عملیاتی این چنین است. همچنین استفاده از این فرمت جدید در مسایل پیچیده و دارای عملیات زیاد، بسیار ساده تر و مطمئن تر است.

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

عملگرهاي Overload شونده
همانطور که تا کنون ملاحظه نموده اید، تمامی عملگرهای زبان C# دارای پیاده سازی داخلی هستند که می توان از این عملگرها در هر عبارتی استفاده نمود. اما مواردی نیز وجود داشتند که برای سهولت کار می توانیم عملگر خاصی را بطور مورد نظر خود پیاده سازی نماییم. پس از اینکه عملگری Overload شد، پیاده سازی انجام شده توسط کاربر بر پیاده سازی پیش فرض تقدم پیاده کرده و تنها در صورتیکه عملگر Overload شده دارای پیاده سازی نباشد، از پیاده سازی از پیش تعریف شده استفاده خواهد شد. همانطور که می دانید در C# دو نوع عملگر وجود دارد. عملگرهای یگانی (Unary) که قابل Overload کردن هستند به شرح زیر می باشند :

كد:

+   -   !   ~   ++   --   true   false



عملگرهای باینری قابل Overload شدن میز به شرح زیر می باشند :

كد:

+   -   *   /   %   &   |   ^   <<   >>   ==   !=   >   <   >=   <=



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

توجه نمایید که تنها عملگرهای مشخص شده در بالا قابلیت Overload شدن را دارند و نمی‌توان سایر عملگرهای زبان C# را Overload نمود. همچنین توجه کنید که Overload کردن یک عملگر باعث Overload شدن ضمني سایر عملگرهای مرتبط با آن نیز می‌شود. براي مثال Overload كردن عملگر + باعث Overload شدن ضمني عملگر تركيبي += نيز مي‌شود. البته توجه نماييد كه عملگر انتساب يا = هيچ‌گاه Overload نمي‌شود.

نكته ديگري كه بايد در مورد عملگرهاي Overload شده در نظر گرفت آنست كه، Overload كردن عملگرها خواصي نظير حق تقدم و يا شركت‌پذيري عملگر را تغيير نمي‌دهد. بعنوان مثال عملگر /، عملگري باينري با حق تقدم مشخص و شركت‌پذيري از چپ است.

پياده‌سازي عملگر Overload شده
پياده‌سازي يك عملگر Overload شده تقريباً شبيه به پياده‌سازي متدي استاتيك است، با اين تفاوت كه در اينجا لازم است از كلمه كليدي operator و عملگر مورد نظر استفاده نماييم. در زير نمونه‌اي از ساختار كلي عملگر ضرب كه براي ماتريسها در نظر گرفته بوديم را مشاهده مي‌نماييد.

كد:

public static Matrix operator *(Matrix mat1, Matrix mat2)

{

    // dot product implementation

}



همانطور كه مشاهده مي‌كنيد، متد استفاده شده حتماً بايد بطور استاتيك تعريف گردد. از كلمه كليدي operator نيز پس نوعي كه مي‌خواهيم براي آن عملگري را Overload نماييم، قرار مي‌گيرد كه در اينجا Matrix نوع مورد نظر ما است. پس از كلمه كليدي operator، عملگري كه مي‌خواهيم Overload كنيم را قرار داده و سپس پارامترهايي كه عملگر بر روي آنها اعمال مي‌شوند را قرار مي‌دهيم. در مثال 1-18، نمونه‌اي از Overload كردن عملگرها را مشاهده خواهيد كرد.

مثال 1-18 : نمونه‌اي از Overload كردن عملگرها

كد:

using System;
 
class Matrix3D
{
    public const int DIMSIZE = 3;
    private double[,] matrix = new double[DIMSIZE, DIMSIZE];
 
    // امكان تخصيص مقدار را براي فراخواننده فراهم مي‌كند.
    public double this[int x, int y]
    {
        get { return matrix[x, y]; }
        set { matrix[x, y] = value; }
    }
 
    //   كردن عملگر + براي استفاده بر روي ماتريسهاOverload
    public static Matrix3D operator +(Matrix3D mat1, Matrix3D mat2)
    {
        Matrix3D newMatrix = new Matrix3D();
 
        for (int x=0; x < DIMSIZE; x++)
            for (int y=0; y < DIMSIZE; y++)
                newMatrix[x, y] = mat1[x, y] + mat2[x, y];
 
        return newMatrix;
    }
}
 
class MatrixTest
{
    //  از آن استفاده مي‌شود.InitMatrixدر متد
    public static Random rand = new Random();
 
    static void Main()
    {
        Matrix3D mat1 = new Matrix3D();
        Matrix3D mat2 = new Matrix3D();
 
        // ماتريسها با مقادير تصادفي مقداردهي مي‌شوند.
        InitMatrix(mat1);
        InitMatrix(mat2);
 
        // ماتريسها در خروجي نمايش داده مي‌شوند.
        Console.WriteLine("Matrix 1: ");
        PrintMatrix(mat1);
        Console.WriteLine("Matrix 2: ");
        PrintMatrix(mat2);
 
        // عمل جمع ماتريسها صورت گرفته و نتيجه محاسبه مي‌گردد.
        Matrix3D mat3 = mat1 + mat2;
 
        Console.WriteLine();
        Console.WriteLine("Matrix 1 + Matrix 2 = ");
        PrintMatrix(mat3);
    }
 
    // متدي كه در آن ماتريسها با مقادير تصادفي مقداردهي مي‌شوند.
    public static void InitMatrix(Matrix3D mat)
    {
        for (int x=0; x < Matrix3D.DIMSIZE; x++)
            for (int y=0; y < Matrix3D.DIMSIZE; y++)
                mat[x, y] = rand.NextDouble();
    }
 
    // متد چاپ ماتريس در خروجي.
    public static void PrintMatrix(Matrix3D mat)
    {
        Console.WriteLine();
        for (int x=0; x < Matrix3D.DIMSIZE; x++)
        {
            Console.Write("[ ");
            for (int y=0; y < Matrix3D.DIMSIZE; y++)
            {
                // فرمت‌دهي خروجي.
                Console.Write("{0,8:#.000000}", mat[x, y]);
 
                if ((y+1 % 2) < 3)
                    Console.Write(", ");
            }
            Console.WriteLine(" ]");
        }
        Console.WriteLine();
    }
}



در مثال 1-18،‌ عملگر + مورد Overload شدن قرار گرفته است. براي تمركز بيشتر بر روي كد، قسمت مربوط به Overload شدن عملگر + را در زير آورده‌ام :

كد:

public static  Matrix3D operator +(Matrix3D mat1, Matrix3D mat2)
    {
        Matrix3D newMatrix = new Matrix3D();
 
        for (int x=0; x < DIMSIZE; x++)
            for (int y=0; y < DIMSIZE; y++)
                newMatrix[x, y] = mat1[x, y] + mat2[x, y];
 
        return newMatrix;
    }



عملگر هميشه بطور استاتيك اعلان مي‌شود، چراكه متعلق به يك نوع كلي است و مربوط به نمونه‌اي خاص نمي‌باشد. نوع بازگشتي، Matrix3D است و تنها چيزي كه اين متد را از يك متد عادي متمايز مي‌نمايد استفاده از كلمه كليدي operator و عملگر + است. پياده‌سازي عملگر Overload شده باعث ايجاد نمونه‌اي جديد از Matrix3D شده و عمل جمع ماتريس را انجام مي‌دهد.

نكاتي چند در مورد Overload كردن عملگرها
زبان C# قوانيني براي Overload كردن عملگرها اعمال مي‌كند. يكي از اين قوانين آنست كه عملگر Overload شده بايد از نوعي كه مورد استفاده قرار مي‌گيرد، اعلان شود.

قانون بعدي اينست كه به هنگام پياده‌سازي عملگرهاي مقايسه‌اي نظير >،
را Overload مي‌كنيد، بايد عملگر >= را پياده‌سازي نماييد.

نكته ديگر اينكه، پس از Overload كردن عملگرها، عملگرهاي تركيبي آنها نيز قابل استفاده هستند. توجه نماييد كه عملگرهاي مقايسه‌اي كه در بالا اشاره شد از اين قاعده مستثنا هستند. همانطور كه در قبل نيز اشاره شد، پياده‌سازي عملگر + باعث مي‌شود تا بتوان از += نيز استفاده نمود