تعتبر تعابير لمدا من الإضافات المفيدة في فيجول بايزيك 2008 حيث يمكنك إعادتها كقيمة من وظيفة أو تمريرها كمحدد لوظيفة أخرى حيث تمت إضافتها للغة البايزيك كدعم للغة الاستعلامات المضمنة Linq التي تضيف إمكانيات استعلامية قوية لبرمجة البيانات في فيجول بايزيك وعندما تبدأ باستخدام تعابير لمدا سترى القوة والمرونة الكامنة فيها
ما هي تعابير لمدا
يشكل الكود التالي مثالا عن تعريف تعبير لمدا أساسي فهو يعرف تعبير لمدا يأخذ Integer ويعيد Integer بحيث يأخذ قيمة الدخل ويعيدها مضروبة بـ 2
Dim doubleIt as Func(Of Integer, integer) = _ Function(x As Integer) x * 2
والنوع Func من الأنواع الجديدة في فيجول بايزيك 2008 وهو في الأساس إجراء مفوض Delegate يعيد نوعا يحدده المحدد الأخير ويمكنك من تمرير أربعة محددات تسبق ذلك المحدد والنوع المفوض Func معرف في المجمع System.Core.dll الأمر الذي يمكنك من الاستفادة منه فوريا وذلك لأن المجمع المذكور يتم استيراده تلقائيا عندما ننشئ تطبيقا جديدا ويمثل الكود التالي تحميلات Overloads مختلفة لـ Func
Dim f0 As Func(Of Boolean) Dim f1 As Func(Of Integer, Boolean) Dim f4 As Func(Of Integer, Integer, Integer, Integer, Boolean)
ففي المثال السابق f0 هو مفوض يعيد قيمة Boolean و f1 مفوض يأخذ Integer ويعيد Boolean و f4 مفوض يأخذ أربعة محددات من النوع Integer ويعيد قيمة من النوع Boolean وتكمن النقطة الأساسية في التعبير لمدا هو أنه مفوض قابل للاستدعاء تماما كالمفوضات في فيجول بايزيك 2005 فمن ناحية المساواة اليمنى في قطعة الكود الأولى يمكنك رؤية الصيغة الجديدة للتعابير لمدا فهي تبدأ بالكلمة المحجوزة Function متبوعة بقائمة من المحددات وتعبير وحيد ففي المثال السابق يأخذ تعبير لمدا محددا واحدا من النوع Integer ونلاحظ عدم وجود تعبير Return وذلك لأن المترجم يعرف النوع المؤسس عليه التعبير وبهذا يقوم بتمرير عبارة Return تلقائيا وفي هذه الحالة بما أن x هو من النوع Integer ونتيجة المعادلة هي Integer لهذا فنتيجة تعبير لمدا هي Integer أيضا ويمكن السحر في تعابير لمدا في أنه يمكن استخدامها كمفوض بسيط كما نرى في المثال
Dim doubleIt As Func(Of Integer, Integer) = _ Function(x As Integer) x * 2 Dim z = doubleIt(20)
فإن نفذت الكود السابق سترى أن القيمة المخزنة في z هي 40 وأنت بهذا قمت بإنشاء تعبير لمدا يضاعف قيمة أي Integer يمرر له.
دعنا الآن نتفحص مثال معقد أكثر باستخدام تعابير لمدا
Dim mult As Func(Of Integer, Func(Of Integer, Integer)) = _ Function(x As Integer) Function(y As Integer) x * y
ويعتبر mult تعبير لمدا معقد قليلا فهو يأخذ كدخل له محدد من النوع Integer ويعيد تعبير لمدا كقيمة له والذي أخذ بدوره قيمة Integer ويعيد قيمة Integer كما يمكننا إعادة تقسيم التعبير السابق على أسطر من أجل توضيح الكود
Dim mult As Func(Of Integer, Func(Of Integer, Integer)) = _ Function(x As Integer) _ Function(y As Integer) x * y
فتعبير لمدا الخارجي يحتوي تعبير لمدا آخر الذي يستخدم من قبل المترجم كقيمة معادة ويكون التوقيع الخاص بتعبير لمدا الداخلي مماثلا لتوقيع المفوض Func(Of Integer, Integer) في القيمة المعادة من تعبير لمدا الخارجي حيث يقوم المترجم بترجمة التعبير بأكمله دون مشاكل ويمكننا رؤية تعبير لمدا هذا كما يلي
Dim mult_10 = mult(10) Dim r = mult_10(4)
فالسطر الأول يحدد mult_10 كـ mult(10) وبما أن Mult(10) يعيد تعبير لمدا يأخذ محدد ويضربه بـ 10 والنوع المعاد من mult_10 هو Func(Of Integer, Integer) والسطر الثاني يستدعي mult_10 ممررا له القيمة 4 بهذا ستكون القيمة المخزنة في r هي 40 ويكون نوع r هو Integer ويعتبر mult مصنع لتعابير لمدا فهو يعيد تعبير لمدا مخصص بالمحدد الأول وستلاحظ أن تعبير لمدا الداخلي يستخدم محدد تعبير لمدا الخارجي ولكن فترة حياة تعبير لمدا الداخلي تتجاوز فترة حياة تعبير لمدا الخارجي
تعابير لمدا كاستدعاءات
بما أن تعابير لمدا هي ببساطة مفوضات لذا يمكنك استخدامها في أي مكان يمكن استخدام المفوض فيه. لاحظ الإجراء التالي الذي يأخذ مفوض كمحدد له ويستدعي مفوض من أجل كل عنصر في القائمة
Delegate Function ShouldProcess(Of T) (element As T) As Boolean Sub ProcessList(Of T) ( _ Elements As List(Of T), shouldProcess As ShouldProcess(Of T)) For Each elem in elements If shouldProcess(elem) Then ' Do some processing here End If Next End Sub
ويكون المثال التالي تطبيقا قياسيا على المفوضات فالطريقة ProcessList ستمر على كل عنصر من القائمة وتتحقق فيما إذا كان عليها معالجة العنصر ثم تقوم ببعض المعالجة وحتى تتمكن من استخدام هذا في فيجول بايزيك 2005 عليك إنشاء وظيفة تمتلك نفس توقيع المفوض ثم تمرر عنوان تلك الوظيفة إلى الإجراء ProcessList
Class Person Public age As Integer End Class Function _PrivateShouldProcess(person As Person) As Boolean Return person.age > 50 End Function Sub DoIt() Dim list As New List(Of Person) 'Obtain list of Person from a database, for example ProcessList(list, AddressOf _PrivateShouldProcess) End Sub
وهذا يسبب بعض الإزعاج فغالبا عليك البحث في توثيق الكود لمعرفة ماذا يمثل توقيع المفوض ثم يجب عليك مطابقته كليا وإن احتجت لاستدعاء ProcessList مع عدة إجراءات ستقوم بإنشاء العديد من الوظائف الخاصة.
دعنا نرى الآن كيف يمكننا استدعاء هذا الإجراء باستخدام تعابير لمدا
Class Person Public age As Integer End Class Sub DoIt() Dim list As new List(Of Person) 'Obtain list of Person from a database, for example ProcessList(list, Function(person As Person) person.age > 50) End Sub
فباستخدام تعابير لمدا لم يعد هناك حاجة لإنشاء وظيفة خاصة للقيام بمنطق المعالجة حيث يتم تعريف المفوض في النقطة التي سيستخدم فيها وهذا أفضل من تعريفه ضمن وظيفة خاصة في مكان ما وفقدان محليتها باستخدام الطريقة الخاصة وبهذا أنت ترى قوة تعابير لمدا وتسهيلها لعملية قراءة وصيانة الكود الخاص بك
لماذا تم تقديم تعابير لمدا
من أجل دعم استعلامات لينك Linq كان يجب إضافة مجموعة من الإمكانيات الجديدة للغة فيجول بايزيك ومن ضمنها كانت تعابير لمدا. افترض أنه لدينا الاستعلام التالي
Dim q = From p In Process.GetProgesses() _ Where p.PriorityClass = ProcessPriorityClass.High _ Select P
فلكي يتم ترجمة هذا التعبير يجري الكثير من العمل تحت الغطاء فالمترجم سيقوم بالمرور عبر المجموعة Process.GetProcesses ويطبق المرشح الموجود في قسم Where عليها ويعيد قائمة بالعمليات التي تطابق ذلك الشرط كما نلاحظ وجود تعبير فيجول بايزيك داخل قسم Where هو p.PriorityClass = ProcessPriorityClass.High وذلك لتطبيق المرشح وهنا يقوم المترجم بإنشاء تعبير لمدا من أجل المرشح الموجود في قسم Where ويطبقه على كل عنصر في قائمة العمليات
Dim q = Process.GetProcesses().Where( _ Function(p) p.PriorityClass = ProcessPriorityClass.High)
وأساسا يشكل التعبير لمدا اختصارا للمترجم من أجل اختصار عملية إنشاء الطرق وربطها مع المفوضات حيث يقوم بكل ذلك من أجلك والفائدة التي نجنيها من تعابير لمدا ولا نجنيها عند استخدام الوظائف والمفوضات هي أن المترجم هنا يستخدم الاستدلال المحلي على النوع على تعابير لمدا ففي المثال السابق يتم تحديد نوع المحدد p بناء على الاستخدام وفي هذه الحالة يحدد التعبير في قسم Where تعبير لمدا ويقوم المترجم بالاستدلال آليا على نوع القيمة المعادة من التعبير لمدا بحيث تعتبر ميزة الاستدلال المحلي على النوع المدعومة من قبل المعالج من الإضافات القوية لفيجول بايزيك
الاستدلال المحلي على النوع
تقديم ميزة الاستدلال المحلي على النوع القوية يعني أنه لم يعد عليك أن تقلق حول تحديد النوع الملائم لكل متغير وبالتالي فهي تمكنك من القيام بالعديد من الأمور التي كانت تبدو مستحيلة فالاستدلال على النوع المعاد من تعابير لمدا مفيد جدا فإن كان لديك نوع مفوض تريد ربطه مع تعبير لمدا لم يعد عليك تحديد نوع جميع المحددات
Dim lambda As Func(Of Integer, Integer) = Function(x) x * x
ففي هذا المثال يكون نوع تعبير لمدا هو Func(Of Integer, Integer) وهو مفوض يأخذ محدد من النوع Integer ويعيد محدد من النوع Integer وكنتيجة لهذا فالمترجم يستدل آليا على أن المحدد x العائد لتعبير لمدا هو من النوع Integer والقيمة المعادة من التعبير لمدا هي Integer أيضا كما يمكنك الاستفادة من الاستدلال على نوع تعابير لمدا عندما تستدعي طريقة تأخذ مفوضا لاحظ الكود التالي
Delegate Function ShouldProcess(Of T) (element As T) As Boolean Sun ProcessList(Of T) (_ Elements As List(Of T), shouldProcess As ShouldProcess(Of T)) ' Method body removed for brevity End Sub
في هذه الحالة تأخذ الوظيفة ProcessList تعبير لمدا ويمكن استدعاؤها على الشكل
Sub DoIt() Dim list As new List(Of A) ' fill or obtain elements in list ProcessList(list, Function(a) a.x > 50) End Sub
لاحظ أننا لم نحدد نوع المحدد الممرر للتعبير لمدا كما فعلنا سابقا وذلك لأن المعالج يستدل عليه بنفسه.
كيف يمكن حدوث شئ كهذا؟ في الحقيقة هناك عدة مستويات من الاستدلال على النوع في هذا المثال ففي البداية يرى المترجم ProcessList كإجراء عادي يأخذ list(Of T) كدخل له و ShouldProcess(Of T) في استدعاء ProcessList ويرى المترجم أن list هي المحدد الأول وأنها list(Of Person) وبما أن المحدد الثاني لا يوفر تلميحات حول ماهية نوع T فيقرر المترجم أن T من النوع Person ويستدل من هذا على أن محدد ShouldProcess(Of T) هو من النوع Person وبهذا يستدل على أن المحدد الثاني هو من النوع ShouldProcess(Of T) وأخيرا بما أن تعبير لمدا لا يقدم نوع المحدد الخاص به والمترجم يعرف أن نوع المحدد يعتمد على توقيع
المفوض ShouldProcess(Of T) وقد استدل على أن نوع المحدد a هو Person ويعتبر هذا نوعا قويا من الاستدلال على النوع فليس عليك معرفة نوع محددات المفوض عندما تبني تعبير لمدا وفي الحقيقة من الأفضل ترك المترجم يقوم بذلك العمل نيابة عنك والاستدلال على نوع النتيجة بهذه الطريقة مفيد حقيقة إن لم يكن لديك نوع مفوض وتريد من المترجم أن يقوم بتصنيعه من أجلك علما بأن هذه الميزة متوفرة في فيجول بايزيك فقط
Dim lambda = Function(x As Integer) x * x
ففي المثال السابق بما أن المحدد x هو من النوع Integer فالمترجم يستدل آليا على أن القيمة المعادة هي من النوع Integer أيضا كنتيجة المعادلة الموجودة في التعبير وبما أن تعبير لمدا لا يمتلك نوعا لهذا يقوم المترجم بتصنيع مفوض مجهول يطابق شكل تعبير لمدا ويربط ذلك النوع المفوض بتعبير لمدا. وهذه ميزة عظيمة لأنها تعني أنه يمكنك إنشاء تعابير لمدا بسرعة بدون أن تحتاج لتعريف الأنواع المفوضة الخاصة بها. فكم مرة كنت في وضع تحتاج فيه لتطبيق مجموعة من المتغيرات وتحتاج إلى فعل ذلك في العديد من الأماكن ففي الكود التالي مرت عدة حالات مشابهة وعادة يمكننا معالجة ذلك بحيث يمكن التحقق من الشرط في مكان واحد بدلا من التشتت في أرجاء الوظيفة
Class Motorcycle Public color As String Public CC As Integer Public weight As Integer End Class Sub PrintReport(motorcycle As New Motorcycle) If motorcycle.color = "Red" And motorcycle.CC = 600 And _ Motorcycle.weight > 300 And Motorcycle.weight < 400 Then ' do something here End If ' do something here If motorcycle.color = "Red" And motorcycle.CC = 600 And _ Motorcycle.weight > 300 And Motorcycle.weight < 400 Then ' do something here End If End Sub
وفي بعض الأحيان يستخدم هذا التحقق في هذه الوظيفة فقط ويمكننا إضافة إجراء في الفئة لدعم تلك الوظيفة فقط والقيام بذلك يؤثر على عملية صيانة الكود فماذا لو قام أحد ما باستدعاء هذه الوظيفة في مكان آخر واحتجت للقيام بتعديل ما وقد يؤدي هذا في بعض الفئات إلى وجود وظائف يصعب تعقبها جاعلا خاصية IntelliSense اٌقل فائدة لوجود العديد من المدخلات الإضافية فيها إضافة إلى خرق منطق المحلية وإن قمنا بذلك باستخدام طريقة منفصلة مختلفة عندها يفضل أن تكون قريبة من الطريقة التي تستخدمها ومع وجود العديد من الأشخاص يعملون على نفس المشروع يصبح من الصعب صيانة المحلية على المدى الطويل وهنا يأتي استخدام تعابير لمدا وترك المترجم يقوم آليا بإنشاء المفوضات ويقوم باستخدامها عند الحاجة
Sub PrintReport(motorcycle As New Motorcycle) Dim check = Function(m As Motorcycle) m.color = "Red" And _ m.CC = 600 And _ m.weight > 300 And _ m.weight < 400 If check(motorcycle) Then ' do something here End If ' do something here If check(motorcycle) Then ' do something here End If End Sub
قمنا هنا بتعديل منطق تفحص بعض شروط Motorcycle ليستخدم تعابير لمدا عوضا عن سيئات الطرائق الخاصة حيث سيقوم المترجم تلقائيا بإنشاء النوع المفوض ويقوم بالعمل لكي نستطيع استدعاء تعابير لمدا أينما احتاج ذلك وهذه الطريقة مفيدة لأنها تضع المنطق قريب من التصريح حيث نقوم بتصنيع نسخة واحدة ويقوم المترجم بعدها بمعظم عمليات الصيانة ويعتبر هذا مفيدا لأنه يمكنك من بناء تعبير معقد كجسم لتعبير لمدا وباستخدام الربط المتأخر والاستدلال على النوع في هذا السيناريو فلا نحدد نوع تعبير لمدا أو المتغير
Dim lambda = Function(x) x * x
وهنا أيضا يولد المعالج مفوض مجهول من أجلك ولكن يحدد نوع تعبير لمدا كـ System.Object وهذا يعني أنه قد تم تفعيل الربط المتأخر في هذا السيناريو عندما يكون الخيار Option Strict على الوضع Off ويعتبر هذا السيناريو جيدا بالنسبة لأولئك الذين يعتمدون على الربط المتأخر حيث أن تعابير لمدا تدعم عمليات الربط المتأخر بشكل كامل ففي المثال السابق طالما أن المعامل * معرف على الأنواع الممررة إلى تعبير لمدا فسوف يعمل
Dim a = lambda(10)
Dim b = lambda(CDec(10))
Dim c = lambda("This will throw an exception because " & _
"strings don't support the * operator")وكما ترى من المثال السابق طالما أن المعامل * موجود في مكتبات زمن التشغيل بالنسبة للنوع الممرر فسوف يجري كل شئ بشكل جيد كما أن تعابير لمدا تتأقلم بشكل رائع مع الربط المتأخر في فيجول بايزيك