Overload کردن عملگرها در C#

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

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 کردن عملگرها، عملگرهای ترکیبی آنها نیز قابل استفاده هستند. توجه نمایید که عملگرهای مقایسه‌ای که در بالا اشاره شد از این قاعده مستثنا هستند. همانطور که در قبل نیز اشاره شد، پیاده‌سازی عملگر + باعث می‌شود تا بتوان از += نیز استفاده نمود.

 

  
نویسنده : ali gooliof ; ساعت ٤:٢۳ ‎ب.ظ روز ۱۳۸٧/٢/٦
تگ ها :