Anonymous Methods
هذه الميزة قد تتيح تسهيلات كثيرة للمبرمجين عند تعاملهم مع ال Delegates . في السي# استخدام الdelegate قد يكون غريبا قليلا عن استخدام التوابع في سي++ . ففي السي++ إذا أردت تمرير مؤشر التابع إلى تابع ما تقوم فقط بكتابة اسم التابع . أما في السي# يجب عليك أن تنشئ كائن جديد delegate يحتوي التابع الذي تريد تمريره .
في الاصدار الجديد من سي# تمت تحسين هذه الطريقة في تمرير التوابع ، والآن بدلا من أن تنشئ delegate خاص يمكنك فقط كتابة اسم التابع تماما كما في سي++ :
// في سي# القديمة button1.Click += new EventHandler(button1_Click); // في سي# الاصدار الجديد button1.Click += button1_Click
والمثال التالي يوضح كيف يمكن تمرير اسم التابع فقط لتابع آخر بدلا من انشاء delegate خاص:
class Program {
static void Main() {
ThreadPool.QueueUserWorkItem(MyCallbackFunction, 10);
}
static void MyCallbackFunction(object o) {
Console.WriteLine(o);
}
}بالاضافة إلى كل السابق فتم اضافة تسهيلات جديدة في سي# الاصدار الثاني وهي تشابه ميزات موجودة في JavaScript ولكن أفضل بكثير. حيث يمكنك أن تكتب التابع Callback Function مباشرة أثناء تمريره بدلا من تعريف تابع منفصل .
class Program
{
static void Main(string[] args)
{
System.Threading.Timer timer = new System.Threading.Timer(
delegate(Object obj) { MessageBox.Show("Hello :)"); }, 10, 1000, 1000);
Console.ReadLine();
}
}لاحظ كيف قمنا بكتابة الكود الذي يظهر الرسالة Hello مباشرة أثناء انشائنا للTimer . الكود السابق سوف يظهر الرسالة Hello كل ثانية حتى يتم انهاء البرنامج.
الآن إذا حاولنا رؤية الassembly الناتجة لمعرفة ماذا فعل المترجم للكود الذي كتبناه لنجد أن المترجم قد أنشئ تابع جديد وقام باضافة كود جديد داخل التابع Main (استخدت أداة Lutz Roeder's .NET Reflector من هنا http://www.aisto.com/roeder/dotnet/)
class Program {
private static void Main(string[] args)
{
if (Program.<>9__CachedAnonymousMethodDelegate1 == null)
{
Program.<>9__CachedAnonymousMethodDelegate1 = new TimerCallback(Program.<Main>b__0);
}
Timer timer1 = new Timer(Program.<>9__CachedAnonymousMethodDelegate1, 10, 0x3e8, 0x3e8);
Console.ReadLine();
}
private static void <Main>b__0(object obj)
{
MessageBox.Show("Hello :)");
}
private static TimerCallback <>9__CachedAnonymousMethodDelegate1;
}لاحظ أنه تم تعريف متحول من نوع TimerCallback وهو static واسمه <>9__CachedAnonymousMethodDelegate1 . إذا لاحظنا داخل Main فإن هذا المتحول سوف يحتوي على الكائن الذي يحوي مؤشر إلى التابع الجديد الذي انتجه المترجم . هذا التابع اسمه <Main>b__0 وهو يحتوي الكود الذي كتبناه أصلا .
بالمختصر فإن العملية ليست معقدة كثيرا حتى الآن ، فالمترجم يقوم وراء الكوليس بعمل كل هذه الأمور بشكل آلي . لاحظ أنه لا توجد اي مشكلة إذا حاولنا من داخل الanonymous method الوصول إلى متحولات معرفة داخل الكلاس لأن التابع الجديد الذي أنشئه المترجم هو أساسا داخل الكلاس. انظر المثال:
class Program
{
static int counter = 0;
static void Main(string[] args)
{
Timer timer = new Timer(
delegate(Object obj) { Console.WriteLine(counter++); }, 10, 1000, 1000);
Console.ReadLine();
}
}تنفيد البرنامج السباق سوف يظهر الأرقام 0 ، 1 ، 2 ، 3 ، 4 ، ....
أحب أن أذكر أيضا أنه ليس من الداعي ذكر البارامترات عند كتابتك لل anonymous method إذا لم تكن تستخدمهم من الداخل . على سبيل المثال يمكن كتابة الكود السابق كالتالي:
class Program
{
static int counter = 0;
static void Main(string[] args)
{
Timer timer = new Timer(
delegate { Console.WriteLine(counter++); }, 10, 1000, 1000);
Console.ReadLine();
}
}الآن ، ماذا لو فكرنا قليلا وحاولنا الوصول إلى متحولات معرفة ضمن التابع الأساسي من داخل الanonymous method .. كما رئينا سابقا فإن التابع الذي ينشئه المترجم هو تابع منفصل عن التابع الأساسي ، فكيف يمكن الوصول إلى المتحولات المعرفة داخل التابع الأساسي!؟ .. انظر المثال التالي:
class Program
{
static void StartTimer()
{
int counter = 0;
Timer timer1 = new Timer(
delegate { Console.WriteLine("Timer1 = {0}", counter++); }, 10, 1000, 1000);
}
static void Main(string[] args)
{
StartTimer();
Console.ReadLine();
}
}نتيجة تنفيذ البرنامج السابق هي:
0
1
2
وهكذا ...
لاحظ أنه قمنا باستخدام المتحول الداخلي counter من ضمن ال anonymous method . وبعد اشائنا للمؤقت فإننا نخرج من التابع مباشرة ، ونرجع إلى التابع Main . ولكن ال counter هي عبارة عن متحول تم انشائه في ال stack ويتم حذفه مباشرة عند الخروج من التابع StartTimer . ولكن كيف عمل البرنامج بصورة صحيحة؟! وكأن المتحول counter مازال على قيد الحياة .
لو نظرنا إلى الكود المنتج من المثال السابق داخل الأداة .NET Reflecotr ، لوجدنا أن المترجم قال بعمل اضافي هنا، انظر إلى الكود الجديد الذي أنتجه المترجم:
class Program
{
private static void Main(string[] args) {
Program.StartTimer();
Console.ReadLine();
}
private static void StartTimer() {
Program.<>c__DisplayClass1 class1 = new Program.<>c__DisplayClass1();
class1.counter = 0;
Timer timer1 = new Timer(new TimerCallback(class1.<StartTimer>b__0), 10, 0x3e8, 0x3e8);
}
private sealed class <>c__DisplayClass1 {
// Methods
public <>c__DisplayClass1() {}
public void <StartTimer>b__0(object) {
Console.WriteLine("Timer1 = {0}", this.counter++);
}
// Fields
public int counter;
}
}المترجم قام باضافة كلاس جديد يحتوي على الanonymous method الذي قمنا بكتابته بالاضافة إلى متحول counter . لاحظ داخل التابع StartTimer فإنه تم انشاء هذا الكلاس الجديد واسناد قيمة للمتحول counter ومن ثم استخدام التابع عندم انشاء المؤقت .. أي بالمختصر فإن المترجم قام بعمل كلاس جديد لتغليف المتحولات التي تم تعريفها داخل التابع الأساسي.
جميل جدا بأن تتيح سي# تسهيلات عديدة في استخدام ال Anonymous Method . ولكن كنصيحة مني يفضل أن لا يزيد تابع ال anonymous method عن ثلاث أسطر!! لإن كثرة استخدامها وخصوصا إذا كان التابع كبير قد يجعل من مراجعة وتنقيح البرنامج صعبا جدا .
>> انتهى <<
