واگذاری (برنامه‌نویسی شیءگرا)

واگذاری یا تفویض (به انگلیسی: delegation) در برنامه‌نویسی شیءگرا، به «ارزیابی» یک عضو (ویژگی یا شگرد) از یک شیء (گیرنده) در زمینه شیء اصلی دیگر (فرستنده) گفته می‌شود. واگذاری را می‌توان به صورت صریح انجام داد، در این حالت شیء فرستنده به شیء گیرنده ارسال می‌شود، این موضوع را می‌توان در هر زبان شیءگرا انجام داد؛ یا اینکه واگذاری را به صورت ضمنی انجام داد، این کار از طریق «قواعد جستجوی عضو» در آن زبان انجام می‌شود، که نیاز به پشتیبانی‌هایی توسط آن زبان در آینده دارد. واگذاری ضمنی، شگرد اساسی برای استفاده مجدد از رفتار، در برنامه‌نویسی مبتنی بر پیش‌الگو است، و این موضوع مشابه با وراثت در برنامه‌نویسی مبتنی بر کلاس است. معروف‌ترین زبان‌هایی که از واگذاری در سطح زبان پشتیبانی می‌کنند، در درجه اول، زبان سلف (به انگلیسی: Self) است که از مفهوم واگذاری از طریق مفهوم «شکاف‌های (اسلات‌ها) ی والد قابل تغییر» استفاده می‌کند، این اسلات‌ها، بر اساس جستجوی شگرد در «فراخوانی‌های خودی» استفاده می‌شوند، و سپس زبان جاوا اسکریپت است (واگذاری جاوااسکریپت را ببینید).

نماینده‌ها در سی شارپ در یک نگاه می‌توان گفت نماینده‌ها (Delegates) نوع داده‌ای در دات نت هستند که برای نگهداری آدرس متدها استفاده می‌شوند.

اینچنین متصور بشید که یک متغیر از یک نوع داده‌ایِ نماینده (delegate) تعریف می‌کنید، سپس تعدادی متد (هم امضاء با نماینده) را بهش اضافه می‌کنید. سپس با invoke کردن، همه متدها اجرا می‌شوند. دقت شود که متدها سریال و نه موازی اجرا می‌شوند. معمولاً به ترتیب اضافه کردن.

امضای متد ویرایش

هر متد دارای یک نوع داده‌ای برگشتی (void راهم یک نوع داده‌ای در نظر بگیرید) و صفر یا چند آرگومان ورودی/خروجی می‌باشد. این دو ویژگی امضای یک متد نامیده می‌شود. توجه شود که تعداد آرگومان‌ها، نوع داده‌ای آرگومان‌ها، ورودی یا خروجی بودن آرگومان و جابه جایی موقعیت آرگومان‌ها امضای متد را تغییر می‌دهند. در سی شارپ ما قادر به نوشتن متدهای بی‌نام و نوع‌های داده‌ای نامشخص (ANONYMOUS Types) هستیم. سی شارپ در این زمینه کاملاً قوی عمل می‌کند.

تعریف نماینده و ثبت متدها در متغیر نماینده ویرایش

شیوه تعریف یک نماینده به صورت زیر است:

public delegate void MyDelegate(int n);
  • public: میزان دسترسی به نوع
  • delegate: کلمه کلیدی برای تعریف نماینده
  • void: نوع برگشتی متدهای پذیرنده (بسته به امضای متد)
  • (int n): آرگومان‌های متدهای پذیرنده (بسته به امضای متد)

از تعریف بالا متوجه می‌شویم که این نماینده متدهایی که امضای آن‌ها شامل: نوع برگشتی void و یک آرگومان ورودی از نوع int باشد را می‌پذیرد.

نماینده‌ها یک نوع داده‌ای مستقل هستند یعنی می‌توان آن‌ها را مثل کلاس‌ها و شمارنده مستقیماً در یک فضای نام (namespace) ایجاد کرد و لزومی ندارد که حتماً داخل یک کلاس تعریف شوند.

تعریف نماینده در یک فرم ویندوزی

namespace Rme_Delegates
{
    public delegate void MyDelegate(int n);// تعریف نماینده

    public partial class Form1: Form
    {
      private MyDelegate _delVar;// تعریف متغیر از روی نماینده

        public Form1()
        {
            InitializeComponent();
        }

        private void MethodXWYZ(int no)
        {
            this.listBox1.Items.Add("MethodXWYZ: "+no);
        }

        protected virtual void MethodEXPse(int pwr)
        {
            this.listBox1.Items.Add("MethodEXPse: " + (pwr * pwr));
        }
    };
}

متغیر خصوصی _delVar در شروع کار مقدار null دارد. متد تعریف شده را با علامت + به _delVar اضافه می‌کنیم:

_delVar += new MyDelegate(this.MethodXWYZ);

به نحوه اضافه کردن متد به متغیر نماینده توجه کنین!!! فقط نام متد به تنهایی لازم است. کلمه this اختیاری است.

و خلاصه تر: روش اول گویا تر است

_delVar += this.MethodXWYZ;
_delVar += this.MethodEXPse;

روش حذف متد نیز استفاده از - (منها) است بدین صورت:

_delVar -= this.MethodXWYZ;
_delVar -= this.MethodEXPse;

در ضمن: اگه نیاز به ثبت فقط یک متد در متغیرنماینده دارید از = (مساوی، انتساب) استفاده کنید:

_delVar = this.MethodEXPse;

فراخوانی (invoke) متدهای یک متغیر نماینده ویرایش

قبل از فراخوانی هر متغیر نماینده باید از تهی نبودن آن اطمینان حاصل کنید. این مسئله در برنامه‌های چند نخی خیلی بیشتر مورد نیاز است. فراخوانی یک متغیر نماینده‌ای همانند فراخوانی متد می‌باشد با این تفاوت که این بار پرانتز را جلوی یک متغیر باز می‌کنیم نه یک متد! مثال زیر نحوه فراخوانی را نمایش می‌دهد:

if (_delVar != null) _delVar(25);

بدین طریق متدهای ثبت شده در متغیر یکی پس از دیگری با مقدار (اینجا ۲۵) اجرا می‌شوند.

متدهای بی نام ویرایش

نحوه اضافه کردن (ثبت کردن) متد به نماینده‌ها در (۲ تعریف نماینده و ثبت متدها در متغیر نماینده) توضیح داد شد. اما در برنامه‌نویسی‌های حرفه‌ای همیشه روال کار بدین صورت نیست. تعریف زیاد متدهای کوچک وقت گیر و باعث پیچیده‌تر شدن کد و در نهایت سردرگمی می‌شود. متدهای بی نام ترفندی است که کامپایلر سی شارپ و نه صرفاً CRL جهت رفع این مشکل به کار می‌برد. این مورد در LINQ بیشتر فواید خود را نشان می‌دهد. در کل زمانی که به متدهای خطی نیاز باشد و آدرس و نام و نشان متد مهم نباشد از متدهای بی نام استفاده می‌شود. متدهای بی نام در دو دسته قرار می‌گیرند:

  • متدهای بی نام با استفاده از کلمه delegate
  • عبارت لاندا Lambda Expression

متدهای زیر را در نظر بگیرید:

private static void methodOne()
{
    Console.WriteLine("Empty method");
}
private static void methodTwo(int number)
{
    if (number >= ۱۰)
        Console.WriteLine("Yes");
    else Console.WriteLine("No");
}
private static void methodThree(int number, bool state)
{
    if (number >= ۱۰)
        Console.WriteLine(state ? "Yes": "No");
    else Console.WriteLine(state ? "True": "False");

}

برای هر سه متد نماینده متناسب را تعریف می‌کنیم:

internal delegate void DelegateOne();
internal delegate void DelegateTwo(int n);
internal delegate void DelegateThree(int n, bool b);

نحوه تعریف متغیرهای نماینده‌ای و ثبت متدها و اجرای آن‌ها بدین صورت است:

DelegateOne d1 = Program.methodOne;
DelegateTwo d2 = Program.methodTwo;
DelegateThree d3 = Program.methodThree;

d1();
d2(10);
d3(10, true);

Console.ReadKey();//pause screen

تا به حال روش استاندارد که در بخش قبل توضیح داده شده بود را مرور کردیم. همین کار را با متدهای بی‌نام و با کلمه کلیدی delegate انجام می‌دهیم:

تعریف و استفاده از متدهای بی نام

DelegateOne d1 = delegate { Console.WriteLine("Empty method"); };
DelegateTwo d2 = delegate(int number)
{
    if (number >= ۱۰)
        Console.WriteLine("Yes");
    else Console.WriteLine("No");
};
DelegateThree d3 = delegate(int number, bool state)
    {
        if (number >= ۱۰)
            Console.WriteLine(state ? "Yes": "No");
        else Console.WriteLine(state ? "True": "False");
    };

d1();
d2(10);
d3(10, true);
Console.ReadKey();//pause screen

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

عبارت لاندا Lambda Expression ویرایش

با توجه به تعریف‌ها مثال (متدهای بی‌نام) عبارت لاندا را در مثال اینچنین بیان می‌کنیم:

DelegateOne d1 = () => Console.WriteLine("Empty method");
DelegateTwo d2 = (number) =>
{
    if (number >= 10)
        Console.WriteLine("Yes");
    else Console.WriteLine("No");
};

DelegateThree
d3 = (number, state) =>// first
    {
        if (number >= 10)
            Console.WriteLine(state ? "Yes": "No");
        else Console.WriteLine(state ? "True": "False");
    };
d3 += (int number, bool state) =>//second
    {
        if (number >= 10)
            Console.WriteLine(state ? "Yes": "No");
        else Console.WriteLine(state ? "True": "False");
    };
d1();
d2(10);
d3(10, false);

Console.ReadKey();//pause screen

در تعریف عبارت لاندا از کلیدواژه => استفاده می‌شود. نوشتن نوع آرگومان‌ها اختیاری است.

کاربرد نماینده‌ها و رخدادها (events) ویرایش

نماینده‌ها در تعریف رخدادها کاربردی ذاتی دارند. همانگونه که شی گرایی بدون رخدادها بی‌معنی می‌شود. یک رخداد در طول زمان احتمالاً رخ خواهد داد. برای نمایش عکس‌العمل مناسب در هین رخ داد متد/هایی باید ثبت نام کرده باشند. متدهای ثبت نام شده در متغیر نماینده‌ای متناسب با رخداد ذخیره می‌شوند. کد زیر تعریف نماینده و رخداد پایان (Terminate Event) را شرح می‌دهد.

internal delegate void DelegateFormEvent();

private static DelegateFormEvent _delOfEvent;
public static event DelegateFormEvent Terminate
{
    add { _delOfEvent += value; }
    remove { _delOfEvent -= value; }
}

متدهای لازم را اینچنین در رخداد ثبت می‌کنیم:

Terminate += () => Console.WriteLine("Handled with me");

در اینجا از عبارت لاندا برای ایجاد و ثبت متد در رخداد استفاده کردیم. همانگونه که مشاهده می‌شود نحوه ثبت/حذف متد در/از یک رخداد دقیقاً مانند ثبت/حذف متد در/از یک متغیر نماینده‌ای می‌باشد. با این تفاوت که می‌توان ثبت متد در رخداد را با بلوک add کنترل کرد و کد مورد نیاز را تهیه کرد. بلوک remove نیز هنگام حذف متد از رخداد اجرا می‌شود. بدین ترتیب می‌توان مدیریت کاملی بر متدهای یک رخداد داشت. مواظب کدنویسی در این قسمت باشید!!!

یک رخداد در زمان مناسب باید برانگیخته شود. برای برانگیختن رخداد اینچنین کد می‌نویسیم:

if (_delOfEvent != null) _delOfEvent();//raise terminate event

برانگیختن یک رخداد برابر است با فراخوانی (اجرا یا invoke) کردن نماینده مربوطه است.

کاربرد نماینده‌ها در Generic ویرایش

کاربرد نماینده‌ها در LINQ ویرایش

پیوند به بیرون ویرایش

منابع ویرایش