سنكمل الاّن ما تم طرحه في القسم الأول من الدرس , يرجى قراءة و فهم الدرس الأول قبل إكمال القراءة , تجد الدرس الأول :
نبدأ مع Generic Delegate :
فلنفترض أننا قمنا بتعريف Delegate كالتالي :
class Program
{
delegate void Do(int num);
static void Main(string[] args)
{
Do del = new Do(print);
del(10);
}
static void print(int n)
{
Console.WriteLine(n);
}
}بسيط , ولا يحتاج للشرح , لكن ماذا عن Method أخرى أسمها printDouble ومعرّفة كالتالي :
static void printDouble(double n)
{
Console.WriteLine(n);
}وأردنا أن نستعمل هذه الـ Method بطريقة تشبه السابقة , سنضطر إلى تعريف Delegate جديد كالتالي :
Delegate void DoDouble(double num);
ونستطيع عندها إستعمال الـ Delegate الجديد كالتالي :
DoDouble del2 = new DoDouble(printDouble); del2(5.6);
وبالتالي إضطررنا إلى تعريف Delegate لكل Method طالما أنهم إختلفوا في التوقيع ...
ولكن نعرف بأن الـ Generic يمكننا من عمل Reusable Data Structure فكما نعلم , الـ Stack مثلا كبنية جاهزة لحمل أي معطيات طالما أنها من نوع معين , فالـ Stack<T> Type وفر علينا كتابة الـ Stack مرة للـ int ومرة للـ String ... فقط نقوم بتعويض إسم الـ Type المراد تخزينه في الـ Stack بالطريقة التالية :
Stack<string> s1 = new Stack<string>();
s1.Push("Hello");
.
.
.
Stack<int> s2 = new Stack<int>();
s2.Push(124);
.
.جميل , ولكن ماذا عن الـ Delegate , هل نستطيع عمل شيء يشبه ما قمنا بعمله في المثال السابق ؟
نعم , لحسن الحظ , فهذا سيخفف التكلفة في تعريف Delegate لكل Method Signature , فنستطيع عمل شيء كالتالي لتعريف Delegate يستطيع حمل مؤشر إلى أي Method تأخذ نفس عدد الـ Signature ولا تقوم بإرجاع شيء :
Delegate void Do<T>(T n);
وبالتالي أصبحنا قادرين بعمل :
class Program
{
delegate void Do<T>(T num);
static void Main(string[] args)
{
Do<int> del = new Do<int>(print);
del(10);
Do<double> del2 = new Do<double>(printDouble); // the Same Do type used , But with Different Generic Parameter type (double this time ...)
del2(142.22);
}
static void print(int n)
{
Console.WriteLine(n);
}
static void printDouble(double n)
{
Console.WriteLine(n);
}
}ممتاز , ولحسن الحظ أيضا أن مايكروسوفت قامت بتضمين عدد كبير من الـ Generic Delegate في الـ Base Class Library وبالتالي , لسنا بحاجة لنقوم بتعريف الـ Generic Delegate خاصتنا , فيوجد تشكيلة وساعة كالتالي :
Delegate void Action(); Delegate void Action<T1>(T1 arg1); Delegate void Action<T1,T2>(T1 arg1 ,T2 arg2); . . . Delegate void Action<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16>( T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15, T16 arg16);
كما ترى , من الصفر إلى الست عشر Parameter (لا أعتقد أن هناك من سيحتاج أكثر من ذلك ).
وأيضا هناك صنف أسمه Func في حال أن الـ Action غير مفيد لك , فقد تكون بحاجة إلى إرجاع قيمة كالـ Factorial التي ترجع نتيجة ,ولا تكتفي بـ void حال الـ Action ...
Delegate TResult Func<TResult>(); Delegate TResult Func<T1,TResult>(T1 arg1); Delegate Tresult Func<T1,T2,TResult>(T1 arg1,T2 arg2); . . . Delegate TResult Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, TResult >(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5,T6 arg6, T7 arg7,T8 arg8,T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15, T16 arg16);
والاّن نستطيع عمل شيء كالتالي , طالما أن كل الـ Delegates المستعملة معرفة مسبقا من أجلنا في الـ BCL ...
class Program
{
static void Main(string[] args)
{
Action<int> del = new Action<int>(print);
del(10);
Action<double> del2 = new Action<double>(printDouble);
del2(142.22);
Func<int, int> del3 = new Func<int, int>(factorial);
del3(10);
}
static void print(int n)
{
Console.WriteLine(n);
}
static void printDouble(double n)
{
Console.WriteLine(n);
}
static int factorial(int n)
{
if (n > 1)
return n * factorial(n - 1);
return 1;
}
}الرجاء أختبار عدة أحتمالات قبل الإنتقال إلى الفقرة التالية ...
Anonymous Methods :
الـ anonymous Method هي Method عادية , قٌدمت في الـ Net 2. , مثلها مثل أي Method أخرى , لكن الفرق الوحيد أنها لا تملك إسما ! , نعم , ويفيد ذلك في سيناريو كالتالي :
class Program
{
static void Main(string[] args)
{
Action<int> del = new Action<int>(print);
del.Invoke(13);
}
static void print(int n)
{
Console.WriteLine(n);
}
}نلاحظ أننا إضطررنا لتعريف method وتسميتها print التي لا تحوي إلا سطر واحد ... لكن مع الـ Anonymous Methods نستطيع حل نفس المشكلة , لكن بطريقة أنظف ... كما يلي :
static void Main(string[] args)
{
Action<int> del = delegate(int n) { Console.WriteLine(n); };
del.Invoke(13);
}
}وبهذه الطريقة إستطعنا التخلص من إسم صريح لـ Method إسمها print , وإستعضنا عن ذلك بنَسب محتويات هذه الـ Method مباشرة ... لمن لم يرى أي فائدة بعد , تخيل معي أعداد هائلة من الـ Methods التي لا تقوم بالكثير , سوى أنها إجبارية الوجود , لأننا سنستعملها في Delegate ما ... وعندما تكبر أعداد الـ Methods تنضٌب مخيلتنا في تسميتها , فقد تصل لمرحلة ما (إذا إتبعت الطريقة التقليدية ) في تسمية Methods بشيء كالتالي : print1 , print2 ,print3 ...
مثال اّخر , أكثر تعقيدا :
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Please write the color number ... ");
int color = Convert.ToInt32(Console.ReadLine());
Action<int, string> del;
switch (color)
{
case 1:
del = delegate(int n, string str) { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine(str); };
break;
case 2:
del = delegate(int n, string str) { Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine(str); };
break;
case 3:
del = delegate(int n, string str) { Console.ForegroundColor = ConsoleColor.Yellow; Console.WriteLine(str); };
break;
case 4:
del = delegate(int n, string str) { Console.ForegroundColor = ConsoleColor.DarkBlue; Console.WriteLine(str); };
break;
default:
del = delegate(int n, string str) { Console.ForegroundColor = ConsoleColor.Gray; Console.WriteLine(str); };
break;
}
del.Invoke(color,"Hello !");
}
}سيقوم البرنامج السابق بقراءة قيمة رقمية من المستخدم , وكل رقم يقابله لون معين لطباعة كلمة "Hello !" ... ( البرنامج ممكن حله بطريقة أسهل بالتأكيد , لكننا لا نقوم بتطريز الفن هنا ... مجرد مثال
)
Lambda Expression :
الـ Lambda Expression لها أساس رياضي مستمد من الـ Functional Programming , وتحديدا تحت إسم Lambda Calculus الذي يبحث فيه من سنة 1930 بشكل نظري ...
الـ Lambda Calculus تتمحور فكرتها بشكل أساسي حول تمثيل الـ Functions وتركيبهم مع بعض ... والموضوع كبير و معقد , لكنه ممتع للغاية , يمكن الرجوع له : http://en.wikipedia.org/wiki/Lambda_calculus
ولكن ما يهمنا هنا هو الشيء الملموس والعملي , فالـ Lambda Expression يفيد بنفس الطريقة التي إستفدنا منها في الـ Anonymous Methods ,قُدم في الـ Net 3. ,مثال مباشر كالتالي :
class Program
{
static int foo(int n)
{
return n * n;
}
static void Main(string[] args)
{
Func<int, int> del1 = delegate(int n) { return n * n; };
Func<int, int> del2 = (int n) => { return n * n; };
Func<int, int> del3 = foo; // or del3 = new Func<int, int>(foo);
//del1 , del2 and del3 are all Equivalent ...
Console.WriteLine("del1 = {0},del2 = {1},del3 = {2}",del1.Invoke(9),del2.Invoke(9),del3.Invoke(9));
}
}ما يهمنا هنا هو :
Func<int, int> del2 = (n) => { return n * n; };إذا كان الـ Lambda Expression بالشكل العام التالي : (input parameters) => expression) , فنلاحظ أنه يتألف من قسمين أسايين :
Input parameters التي واضح من أسمها انها تمثل أسماء الـ parameters التي سيُوصِفها الـ Lambda Expression (فلا تنسى , الـ Lambda Expression توصف Function لكي يتم إسنادها إلى Delegate ) ...
<= وتسمى الـ Lambda Expression Operator وتسمى أحيانأ goes to
Body of Lambda Expression :وهو Body الـ Method الذي يمثلها الـ Lambda Expression ...
فكرة أخرى يجب طرحها , هناك ذكاء فيC# Compiler لحَل نوع (Type) الـ parameters ويرمز لهذه التقنية بالـ Type Inference بالحقيقة هذا الإسم مستمد من تراث الـ Functional Programming ...
فلو قمنا بإهمال كتابة نوع الـ parameters كالتالي :
Func<int, int> del2 = (n) => { return n * n; };فلن يعترض الـ Compiler على ذلك , لأنه تمكن من إستنتاج نوع الـ n من نوع المتحول del2 الذي هو :
Action<int, int> del2;
عدة حالات خاصة :
1- نستطيع تجاهل الأقواس إذا كانت الـ Lambda Expression يمثل Delegate بـ Parameter واحد كالتالي :
Func<int, int> del2 = n => { return n * n; };2- نستطيع تجاهل الـ { } في تمثيل الـ Body إذا كان يحوي سطر وحيد كالتالي:
Func<int, int> del2 = (n) => n * n;
لاحظ أيضا سقوط الـ Return (فلا قيمة لوجودها , يمكن إستنتاج ذلك فعلا من الـ Compiler نفسِه )
3- إذا كنا نقوم بتمثيل Delegate كالتالي :
Delegate void Do();
بالLambda Expression تكتب بالطريقة التالية حصرا :
Do del = ()=> Math.Pow(10, 3);
فالـ () يمثل الـ no Parameters ...
يلاحظ أن Lambda Expression حلت محل الـ Anonymous Methods لأنها أكثر جمالا بصراحة , ومختصرة أكثر ...
Recursion In Lambda Expressions and Anonymous Methods :
أحب التطرق لفكرة مهمة , كيف أقوم بكتابة الـ Recursive Function في كل من Anonymous Methods أو Lambda Expressions ...
فلننظر إلى الـ Factorial Method التالية :
static int factorial(int n)
{
if (n >1)
return n * factorial(n - 1);
return 1;
}فالـ Function تستطيع طلب نفسها ببساطة عند حاجتها لممارسة طقوس الـ Recursion
, لكن المشكلة , كيف ستشير Lambda Expression أو Anonymous Method إلى نفسها ومن أهم مبادئها أنها لا تحوي أسم ؟ ![]()
قد يقول قائل أننا نشير إلى إسم الـ Delegate عوضا عن ذلك , مممم قد إقتربت من الحل فيمكنك كتابة شيء كالتالي مثلا :
class Program
{
static void Main(string[] args)
{
Func<int, int> fact = n =>
{
if (n > 1)
{
return n * fact(n - 1);
}
return 1;
};
fact.Invoke(10);
}
}لكن المشكلة التي ستظهر عند محاولة عمل Compile هي أنك تقوم بإستعمال Unassigned variable ...
لا يوجد مؤامرة ضد الـ Recursive Lambda Expression , فتذكر ! عند محاولتك عمل شيء كالتالي أيضا , ستظهر لك نفس المشكلة :
class Program
{
static void Main(string[] args)
{
int n = n - 1;
}
}يكون الحل كالتالي , بإستعمال "شطارتك" في تجاوز هذه المشكلة بالشكل التقليدي الذي تقوم به عادة في حل مشكلة الـ int السابقة :
class Program
{
static void Main(string[] args)
{
int n = 0;
n = n - 1;
}
}وبالتالي حل الـ Lambda Expression كالتالي :
class Program
{
static void Main(string[] args)
{
Func<int, int> fact = null;
fact = n => n > 1 ? n * fact(n-1) : 1;
Console.WriteLine(fact.Invoke(10));
}
}أو حل الـ Anonymous Method التالي :
class Program
{
static void Main(string[] args)
{
Func<int, int> fact = null;
fact = delegate(int n) {return n > 1 ? n * fact(n-1): 1; };
Console.WriteLine(fact.Invoke(10));
}
}من يحب أن يعرف طريقة أصعب في حل هذا المشكلة تحديدا , فليٌحضّر نفسه لـ"وجع الراس" بقراءة المقال التالية (لأنها تعتمد على Lambda Calculus بشكل كبير ) :
http://blogs.msdn.com/b/madst/archive/2007/05/11/recursive-lambda-expressions.aspx
ألقاكم في الجزء الثالث , بإنتظار الأسئلة ...