نماینده‌ها (برنامه‌نویسی)

نماینده‌ها در سی شارپ در یک نگاه می‌توان گفت نماینده‌ها (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 >= 10)
Console.WriteLine("Yes");
else Console.WriteLine("No");
private static void methodThree(int number, bool state)
if (number >= 10)
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 >= 10)
Console.WriteLine("Yes");
else Console.WriteLine("No");
DelegateThree d3 = delegate(int number, bool state)
if (number >= 10)
Console.WriteLine(state ? "Yes": "No");
else Console.WriteLine(state ? "True": "False");
d1();
d2(10);
d3(10, true);
Console.ReadKey();//pause screen
در اینجا نیازی به تعریف متدها درون کلاس نیست و به صورت خطی در جلوی نماینده مربطه تعریف می‌شوند. دقت شود که نوشتن کلمه delegate اجباری است همچنین برای متدهای دارای آرگومان نوشتن نوع آرگومان‌ها اجباری است.
عبارت لاندا Lambda Experssion
با توجه به تعریف‌ها مثال (متدهای بی‌نام) عبارت لاندا را در مثال اینچنین بیان می‌کنیم:
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
(NET Framework.)