حسام برهان

الأعضاء
  • المساهمات

    115
  • تاريخ الانضمام

  • تاريخ آخر زيارة

السُّمعة بالموقع

66 Excellent

9 متابعين

  1. مدخل إلى Windows 10

    مما لا شكّ فيه أنّ نظام التشغيل ويندوز 10 هو من أفضل أنظمة التشغيل التي أنتجتها مايكروسوفت حتى الآن. وعلى الرغم من أنّه ليس من الضروري أن يكون آخر نظام تشغيل تنتجه مايكروسوفت هو الأفضل دومًا (وهناك عدّة أمثلة على ذلك)، إلّا أنّ ويندوز 10 بحق هو نظام تشغيل يستحقّ أن تنتقل إليه. سنتناول في سلسلة من المقالات حول ويندوز 10 أساسيّات التعامل مع هذا النظام، بالإضافة إلى استكشاف العديد من خصائصه المفيدة والمهمّة بنفس الوقت. سنستهل هذه السلسلة بالحديث عن إصدارين محدّدين من ويندوز 10، حيث سنقارن بينهما من حيث بعض المزايا بالإضافة إلى السعر الحالي لكلّ منهما. كما سنتحدّث عن الترقية من نظام تشغيل أقدم إلى ويندوز 10، أو تنصيب نسخة جديدة تمامًا، حيث سنتحدّث بشيء من التفصيل عن كيفيّة تنصيب ويندوز 10 على الحاسوب الشخصي في المقال القادم. ما الذي يُقدّمه لي ويندوز 10؟ سيقدّم لك ويندوز 10 العديد من المزايا الجديدة والمحسّنة، والتي سنتحدّث عنها تباعًا من خلال هذه السلسلة. إليك بعضًا من هذه المزايا: المساعد الشخصي كورتانا Cortana وهو من أبرز هذه المزايا، رغم أنّ النسخة العربية من ويندوز 10 لا تدعمه حتى لحظة كتابة هذا المقال (توجد خطط لدعمه في المستقبل القريب). أسطح المكتب الافتراضيّة virtual desktops وعارض المهام Task View. وهما ميّزتان متكاملتان تسمحان لنا بمزيد من التنظيم في العمل في ويندوز 10. مركز الصيانة Action Center. يمكن من خلاله الوصول إلى أيّة إشعارات جديدة من نظام التشغيل، أو إشعارات رسائل بريد إلكتروني جديدة (في حال استخدمت تطبيق البريد الذي يأتي مع ويندوز)، كما يمكنك الوصول من خلال هذا المركز إلى إعدادات الحاسوب المختلفة، كما ويمكن التحكّم السريع باتصالات الشبكة اللاسلكيّة Wi-Fi بالإضافة إلى العديد من الإمكانيّات الأخرى. تحسينات كبيرة على البرمجيّات المرفقة مع نظام التشغيل مثل تطبيق البريد الافتراضي، وتطبيق التقويم. لهواة الألعاب هناك تطبيق Xbox مُضمّن في ويندوز 10 يسمح للمستخدم استعراض الألعاب التي يلعبها مع إمكانية مراجعة التعليقات التي وضعها اللاعبون الآخرون، بالإضافة إلى إمكانيّة إضافة دعوة لصديق للعب إحدى الألعاب وغيرها من المزايا. إلّا أنّ أهمّها هو إمكانيّة الاتصال بجهاز Xbox حقيقي، والاستمتاع بلعب ألعابك المفضّلة عن طريق حاسوبك الشخصي الذي يُشغّل ويندوز 10. حيث يتمّ تبادل البيانات بين الحاسوب وجهاز Xbox عن طريق الشبكة المحليّة. إصدارات ويندوز 10 لويندوز 10 عدّة إصدارات وهي: - الإصدار المنزلي Windows 10 Home Edition. - الإصدار الاحترافي Windows 10 Pro Edition. - الإصدار المؤسّساتي Windows 10 Enterprise Edition. - الإصدار التعليمي Windows 10 Education Edition. - إصدار الأجهزة المحمولة Windows 10 Mobile. سنتحدّث في هذه السلسلة بشكل أساسيّ عن الإصدار Windows 10 Pro. يحتوي الإصدار Windows 10 Pro بكلّ تأكيد على جميع المزايا الموجودة في Windows 10 Home بالإضافة إلى مزايا أخرى إضافيّة تناسب عالم الأعمال. يبيّن الجدول التالي بعضًا من أبرز أوجه الاختلاف بين هذين الإصدارين: وهناك اختلاف في السعر بطبيعة الحال، فحتى لحظة كتابة هذا المقال، يبلغ سعر نسخة Home في متجر الولايات المتحدّة ما يقارب 120 دولار. ويبلغ سعر نسخة Pro في نفس المتجر 200 دولار تقريبًا. أمّا بالنسبة للنسخ المخصّصة للشرق الأوسط فهناك تباين واضح بالأسعار حتى بالنسبة للدول العربيّة المتجاورة. قارن بين سعر متجر المملكة العربيّة السعوديّة ووسعر متجر الإمارات العربيّة المتحدّة. هل أختار الترقيّة أم النسخة الجديدة؟ في كلتا الحالتين ستدفع ثمن النسخة كاملةً. ولكن يُعتبر خيار الترقية Upgrade أسهل من ناحية الإجراءات المتّبعة لتثبيت ويندوز 10. وينبغي هنا الانتباه في حالة الترقية إلى نوع نسخة الويندوز التي لديك، هل هي 64 بت أم 32 بت. كما ينبغي أن تنتبه إلى اللغة الحالية المستخدمة في نظام التشغيل قبل الترقية. بالنسبة إليّ فأنا أفضّل النسخة الجديدة. فأنت تنصّب نسخة نظيفة من نظام التشغيل، ودون الحاجة إلى أيّة اعتبارات أخرى باستثناء المتطلّبات الحاسوبيّة الدنيا التي سنتكلّم عنها في الفقرة التالية. المتطلّبات الحاسوبيّة الدنيا لتشغيل ويندوز 10 يتطلّب حاسوبك أن يمتلك المواصفات العتاديّة التالية على أقلّ تقدير كي يتمكّن من تشغيل ويندوز 10: معالج ذو سرعة لا تقل عن 1 جيجا هرتز. ذاكرة وصول عشوائيّة RAM لا تقل عن 1 جيجا بايت لنسخة 32 بت. ولا تقل عن 2 جيجا بايت لنسخة 64 بت من ويندوز 10. مساحة خالية على القرص الصلب لا تقل عن 16 جيجا بايت. بطاقة عرض رسوميّة تدعم DirectX 9. هذه هي المواصفات الأقل. وكلّما كان حاسوبك يمتلك مواصفات أعلى من المواصفات المذكورة كان أدائه أفضل بالطبع. وعلى أيّة حال، إذا كنت تستخدم أصلًا نظاميّ التشغيل Windows 8.1 أو Windows 8 فعلى الغالب أنّك لن تعاني أيّة مشاكل عند الانتقال إلى Windows 10. الخلاصة من المؤكّد أنّ ويندوز 10 من بين أكثر أنظمة التشغيل تميُّزًا التي أطلقتها مايكروسوفت خلال تاريخها. لقد تعلّمت مايكروسوفت الكثير من أخطائها السابقة، حيث تلافت الكثير من الانتقادات التي كانت تُوجّه عادةً إلى أنظمة التشغيل الأقدم. في الحقيقة يخضع ويندوز 10 للتطوير المستمرّ، وهناك تحديثات مستمرّة تختلف بطبيعتها عن تلك التحديثات الروتينيّة التي كانت تجري لأنظمة تشغيلها السابقة. على أيّة حال، أرجو أن تستمتع بهذه السلسلة من المقالات التي تكشف العديد من المزايا الجديدة والمفيدة في ويندوز 10.
  2. انظر هنا: http://www.cplusplus.com/reference/cstring/strcmp/ وهنا: http://www.cplusplus.com/reference/string/string/compare/
  3. مبدأ عكس التابعيّة Dependency Inversion Principle أو اختصارًا DIP، هو آخر مبادئ التصميم الكائنيّ SOLID ويتمتّع بمزايا كبيرة عند تطبيقه بالشكل السليم. أوّل من قدّم هذا المبدأ هو روبرت مارتن في مقالته التي نشرها عام 1996. أشار روبرت إلى أنّ الأسلوب الشائع في تصميم التابعيّة dependency ضمن المشاريع البرمجيّة في جعل الوحدات البرمجيّة عالية المستوى تعتمد على الوحدات البرمجيّة منخفضة المستوى بشكل مباشر هو أسلوب غير عمليّ ويؤدّي إلى مشاكل كبيرة عند إعادة استخدام الوحدات البرمجيّة عالية المستوى، تتمثّل هذه المشاكل في إجراء تعديلات برمجيّة عديدة عليها كي تتلاءم مع الاستخدام الجديد لها، وهذا بالطبع أمر غير جيّد. تُعتبر الوحدات البرمجيّة عالية المستوى بمثابة قلب التطبيق البرمجيّ. وقد نرغب في كثير من الأحيان أن نُعيد استخدامها في تطبيقات برمجيّة أخرى ولكن بدون إجراء تعديلات كبيرة عليها. يقترح روبرت مبدأ عكس التابعيّة والذي ينص على ما يلي: أ – لا ينبغي أن تعتمد الوحدات البرمجيّة عالية المستوى على الوحدات البرمجيّة منخفضة المستوى. يجب على كلّ منهما الاعتماد على واجهات (أصناف مجرّدة). ب – لا ينبغي أن تعتمد الواجهات/الأصناف المُجردّة على التفاصيل، فالتفاصيل هي من يجب أن تعتمد على الأصناف الواجهات. قد يكون من الصعب قليلًا فهم هذا المبدأ مباشرةً، لذلك اسمح لي بتوضيحه بشكل أفضل. يُقرّ هذا المبدأ أنّه ينبغي أن تكون هناك طبقة تجريديّة إضافيّة بين الوحدات البرمجيّة عالية ومنخفضة المستوى، تتألّف الطبقة التجريديّة من واجهات. فإذا كان لدينا وحدتان برمجيّتان أردنا أن تعتمد إحداهما على الأخر، فإنّ هذه الاعتماديّة ينبغي أن تتمّ عن طريق صنف واجهة معرّف خصيصًا لهذا الغرض. بهذا الأسلوب، فإنّ الوحدات البرمجيّة عالية المستوى لا تتعامل مباشرةً مع الوحدات البرمجيّة منخفضة المستوى. ستعمل الوحدات البرمجيّة منخفضة المستوى على تحقيق الواجهات (نستطيع القول أنّها سترث منها)، ففي حال أردنا استخدام أي وحدة برمجيّة في مشروع برمجيّ آخر فلن نحتاج إلى تعديل أيّ شيء ضمن الشيفرة البرمجيّة لها. فكل ما نحتاجه ببساطة هو تحقيق الواجهات التي ستعتمد عليها الوحدة البرمجيّة المراد إعادة استخدامها. بالنسبة للقسم الثاني من المبدأ (الفقرة ب) فهو ينبّه بأنّ الواجهات ينبغي ألّا تصمّم وفقًا للوحدات البرمجيّة منخفضة المستوى (التفاصيل). حيث ينبغي أن تُحقّق الأصناف الوجهات على نفس مستوى التجريد الذي تتمتّع به الوحدات البرمجيّة عالية المستوى. مثال عن عكس التابعيّة لنستعرض الآن مثالًا حول كيفية تطبيق هذا المبدأ. يدور هذا المثال حول التعامل مع الواجهات الرسوميّة للمستخدم، ليكن لدينا صنف يمثّل نافذة Window تحوي زرّين Button: class Button { public: void makeVisible(); }; class Window { Button* okButton; Button* cancelButton; Window() { okButton = new Button; okButton->makeVisible(); cancelButton = new Button; cancelButton->makeVisible(); } }; تكمن المشكلة هنا أنّه إذا تغيّر الصنف Button، سنضطّر إلى تغيير بانية الصنف Window أيضًا، وهذا بالطبع أمر غير مرغوب به، لأنّ الصنف Window سيكون عُرضةً للكثير من الاختبارات أثناء بناء البرنامج، وهذا سيؤدّي إلى الكثير من الأخطاء في كلّ مرّة نُعدّل فيها الصنف Button. يؤدّي استخدام صنف واجهة إلى تحسين التصميم إلى حدّ كبير: class IButton { public: static virtual IButton* getInstance() = 0; // factory method virtual void show() = 0; }; class Window { IButton* okButton; IButton* cancelButton; public: Window() { okButton = IButton::getInstance(); okButton->show(); cancelButton = IButton::getInstance(); cancelButton->show(); } }; class Button : public IButton { public: void show(); }; كما نرى الآن، يوجد صنف واجهة اسمه IButton وكل من الصنفين Button وWindow يعتمدان عليه. وهذا أمر جيّد للغاية، ففي حال أردنا استخدام الصنف Window في تطبيق برمجيّ جديد، فكل ما علينا هو استخدام أزرار Button تُحقّق الواجهة IButton فحسب. نلاحظ أيضًا التّابع الساكن getInstance الذي نستخدمه للحصول على الكائن الملائم من صنف الزر الذي يُحقّق صنف الواجهة IButton. المصادر http://www.objectmentor.com/publications/dip.pdf http://www.oodesign.com/dependency-inversion-principle.html http://en.wikipedia.org/wiki/Dependency_inversion_principle ترجمة -وبتصرّف- للمقال Dependency Inversion Principle لصاحبه Radek Pazdera.
  4. مبدأ فصل الواجهات Interface Segregation Principle أو اختصارًا ISP هو أحد المبادئ الشهيرة من مبادئ SOLID في التصميم الكائنيّ. أوّل من قدّم هذا المبدأ روبرت مارتن في سلسلة مقالات في عام 1996، ويهدف هذا المبدأ إلى تجنُّب إنشاء واجهات "سمينة". الواجهة interface هي عبارة عن تجريد abstraction لناحية وظيفيّة (أو أكثر) ترث منها أنواع (أصناف) لدعم هذه الناحيّة الوظيفيّة، ونقول في هذه الحالة أنّ الصنف الابن يُحقّق الواجهة. فمثلًا تُعرّف الواجهة IComparer في لغة C# إمكانيّة المقارنة بين كائنين يحقّق صنفهما الواجهة IComparer. في لغات برمجة أخرى، قد لا يوجد تمثيل منفصل للواجهة، وإنّما يتم إنشاء أصناف ذات طبيعة مجرّدة لهذا الغرض، نسميها أصناف واجهة، وهي التي سنتحدّث عنها في هذا المقال. تظهر الواجهات السمينة (أو الملوّثة) بسبب توسعة صنف واجهة حالي ببعض النواحي الوظيفيّة الجديدة المفيدة لمجموعة جزئيّة فقط من التوابع methods. تؤدّي هذه الظاهرة في نهاية الأمر إلى إنشاء توابع ليس لها أيّ فائدة سوى تحقيق الواجهة للتمكّن من استخدامها. وهذا أمر سيّء بالطبع. تُعتبر تلك التوابع خطرة وهي تخرق على أيّة حال مبدأ LSP أيضًا. ينص مبدأ ISP كما كتبه المؤلّف: ينبغي أن يكون لكلّ واجهة هدف معرّف بوضوح ويُعبّر عن ناحية وظيفيّة مُحدّدة للمسألة المطروحة. الحل الأمثل (برأيي) لتحقيق هذا المبدأ هو استخدام الوراثة المتعدّدة عند تحقيق الواجهات. سيعمل هذا الأسلوب على فصل النواحي الوظيفيّة التي لا ترتبط منطقيًّا مع بعضها ويلغي الاعتماديّات dependencies الخاطئة. لنستعرض مثالًا يخرق مبدأ ISP. يتناول هذا المثال واجهة خاصّة بالنواحي الوظيفيّة الّتي من الممكن أن تمتلكها أي سيّارة: /* Bad example */ class CarOperation { public: virtual void steer(int degrees) = 0; virtual void pullHandbrake() = 0; virtual void accelerate() = 0; virtual void shift(int gear) = 0; virtual void toggleAirConditioning() = 0; }; هناك العديد من المزايا المألوفة التي توفّرها السيّارات، فكل سيّارة لها عجلة قيادة ومدوسة وقود بالإضافة إلى ناقل حركة يدوي وفرامل اليد. ولكن ماذا عن السيّارات التي لها ناقل حركة أوتوماتيكي؟ ففي مثل هذه السيّارات لا يوجد ناقل حركة يدوي، فالصنف الواجهة الوارد في الشيفرة السابقة سيجبر أي صنف ابن يرث منه على استخدام الطريقة shift التي تُعبّر عن ناقل حركة اليدوي حتى ولو كان هذا الصنف الابن يمثّل سيّارة ذات ناقل أوتوماتيكي. وينطبق نفس الكلام على الطريقة toggleAirConditioning التي تعبّر عن تشغيل جهاز التكييف في السيّارة رغم أنّ بعض السيّارات لا تملك جهاز تكييف. الأفضل في هذه الحالة أن نجزّئ الصنف الواجهة CarOperation إلى عدد من أصناف الواجهات الأصغر بحيث تُعبّر كلّ منها عن مجموعة محدّدة مترابطة منطقيًّا من المزايا: class BasicCarOperation { public: virtual void steer(int degrees) = 0; virtual void pullHandbrake() = 0; virtual void accelerate() = 0; }; class GearboxCarOperation { public: virtual void shift(int gear) = 0; }; class AirConditioningCarOperation { public: virtual void toggleAirConditioning() = 0; }; class AlfaRomeo166 : public BasicCarOperation, GearboxCarOperation, AirConditioningCarOperation { /* Implementation of all the interfaces. */ }; class SkodaFavorit136L : public BasicCarOperation, GearboxCarOperation { /* No air conditioning for old cars. */ }; سيلجأ الصنفان الابنان AlfaRomeo166 وSkodaFavorit136L إلى الوراثة المتعدّدة من الأصناف الواجهات. فإذا أردنا تشغيل جهاز التكييف في سيّارة ما عن طريق الدالّة beCool فسيكون ذلك كالتالي: void beCool(AirConditioningCarOperation* vehicle) { vehicle->toggleAirConditioning(); } نلاحظ أنّنا الدالّة beCool تقبل وسيطًا واحدًا وهو مؤشّر إلى كائن من النوع AirConditioningCarOperation، وهكذا فإنّ أي صنف ابن يرث من الصنف الواجهة AirConditioningCarOperation يمكن أن يُمرَّر إلى هذه الدالة بصرف النظر عن أيّ أصناف واجهات أخرى يرث منها هذا الصنف الابن. وهنا يكمن جمال مبدأ فصل الواجهات. سنحصل تمامًا على ما نريد، ليس أكثر وليس أقل، مما يجعل من الشيفرة البرمجيّة أسهل للصيانة ولإعادة الاستخدام وللفهم، ويساعد ذلك على تجنّب كم كبير من الأخطاء عندما نريد تعديل الشيفرة في المستقبل. المصادر: http://www.oodesign.com/interface-segregation-principle.html http://en.wikipedia.org/wiki/Interface_segregation_principle http://www.objectmentor.com/resources/articles/isp.pdf ترجمة -وبتصرّف- للمقال Interface Segregation Principle لصاحبه Radek Pazdera.
  5. xamarin 101

    أهلًا بك أخي. أعتذر عن التأخر في الرد. لا أدري لماذا لم ينبهني الموقع على تعليقك. على العموم يمكنك مراجعة الرابط التالي الذي يوضّح بالتفصيل كيفية ذلك: https://developer.xamarin.com/guides/android/getting_started/installation/accelerating_android_emulators/
  6. تعلم سي شارب

    أهلًا وسهلًا. هل من الممكن أن أعرف ما هي رسالة الخطأ التي ظهرت لك. وإذا كان من الممكن أن تأخذي لقطة للشاشة يكون أفضل.
  7. حبذا لو توضّح ما هية الـ URL الذي تغيّره.
  8. إذا كنت مصمّمًا على البدء في برمجة تطبيقات تعمل على iOS فستحتاج حكمًا إلى جهاز ماك. والسبب أنّ بيئة التطوير التي ستستخدمها وهي Xcode بالإضافة إلى حزمة التطوير SDK الخاصة بتطوير هذه التطبيقات يمكن استخدامها من خلال نظام تشغيل Mac OS X. توجد ثلاثة حلول برأيي: 1- أن تستخدم نسخة معدّلة من أحد أنظمة تشغيل Mac OS X لتعمل على ويندوز ضمن الآلة الافتراضيّة VirtualBox. وهذه الطريقة برأيي ليست مضمونة تمامًا وقد تحدث بعض المشكلات وخصوصًا عند تطوير التطبيقات، وقد لا تحدث مثل هذه المشكلات. انظر إلى الرابط التالي لتعرف كيف ذلك: http://www.instructables.com/id/How-to-install-OS-X-Mountain-Lion-on-your-PC-with-/ 2- أن تحاول شراء جهاز ماك مستعمل ونظيف بسعر مقبول، وهناك أيضًا حسبما رأيت أجهزة ماك مُعاد تصنيعها refurbished. 3- أن تشتري جهاز ماك ميني. سعره يبدأ بـ 499 دولار، وهناك نماذج متطوّرة أكثر منه. انظر الرابط التالي http://www.apple.com/mac-mini/specs/
  9. لا أنصحك مطلقًا بالتفكير في مثل هذه الطريقة. المجالين اللذين تتحدّث عنهما منفصلين تمامًا ولا يجوز الخلط بهما. إذا كنت تمتلك أحد حواسيب ماك، ولديك جهاز آيفون أو آيباد (ويمكن ألّا تمتلك أيًّا منهما حاليًّا)، ولديك معرفة برمجيّة أوليّة فيمكنك البدء بتعلّم لغة سويفت Swift لتطوير تطبيقات تعمل على iOS. أمّا إذا لم تكن تمتلك حاسوب ماك، ولم تكن مستعدًّا حاليًّا لدفع ثمنه، فالأفضل أن تتجه إلى اتجاه آخر، كتطوير تطبيقات أندرويد مثلًا. هذا بالنسبة لموضوع تطوير تطبيقات تعمل على الأجهزة المحمولة. أمّا بالنسبة لتطوير تطبيقات ويب، فالموضوع متشعّب وهناك الكثير مما يمكن قوله. أحتاج أولًا أن أعرف ماهو وضعك حاليًّا لكي أستطيع تقديم النصيحة الأفضل إن شاء الله.
  10. تعلم سي شارب

    أهلًا أخي محمد. سعيد أنّك استفدت من هذا المقال، وأنا جاهز لأي استفسار حوله. يمكنك زيارة الرابط التالي للاطلاع دومًا على جديد سلسلة تعليم سي شارب: https://academy.hsoub.com/tags/تعلم سي شارب/
  11. يُعتبر التعامل مع النصوص من المهام البرمجيّة الأساسيّة في جميع أنواع التطبيقات بما فيها تطبيقات الأجهزة المحمولة. توفّر Xamarin تقنيّات جيّدة للتعامل مع النصوص، سنتناول العديد منها من خلال هذا الدرس، وأيضًا من خلال دروس لاحقة في هذه السلسلة. سنعالج في هذا الدرس بعضًا من الحالات التي قد تواجه المطوّر عند التعامل مع النصوص في التطبيقات التي يُنشئها. التعامل مع المقاطع النصية نحتاج في الكثير من الأحيان أن نعرض نصًّا طويلًا بعض الشيء على الشاشة. قد يأتي هذا النص من مستند أو من خدمة ويب web service أو من غيرها من المصادر. المتنوّعة، وينبغي أن يتمكّن التطبيق من التعامل مع نصوص بمثل هذا الحجم. كما فعلنا من الدرس السابق، أنشأ مشروعًا جديدًا من النوع (Blank App (Xamarin.Forms Portable وسمّه TextManipulationApp. أبق فقط على المشروعين (TextManipulationApp (Portable و TextManipulationApp.Droid. سنضيف الآن صفحة محتوى content page جديدة ولكن بطريقة مختلفة ومختصرة عما فعلناه في الدرس السابق. انقر بزر الفأرة الأيمن على المشروع (TextManipulationApp (Portable واختر Add ثم من القائمة الفرعية التي ستظهر اختر New Item. اختر من الجهة اليسرى للنافذة التي ستظهر العقدة Cross-Platform، وبعد تحديث محتويات القسم الأوسط من النافذة، اختر نوع الملف Forms ContentPage وامنحه الاسم ParagraphPage من مريع الاسم Name في الأسفل، وبعد ذلك انقر Add. كما في الشكل التالي: بعد أن يضيف Visual Studio هذا الملف، ستكون محتوياته على الشكل التالي: using System; using System.Collections.Generic; using System.Linq; using System.Reflection.Emit; using System.Text; using Xamarin.Forms; namespace TextManipulationApp { public class ParagraphPage : ContentPage { public ParagraphPage() { Content = new StackLayout { Children = { new Label { Text = "Hello ContentPage" } } }; } } } توجد نطاقات أسماء غير ضرورية هنا ولكن لا بأس بها حاليًا. لاحظ أنّ الصنف ParagraphPage يرث من الصنف ContentPage بشكل افتراضي، كما لاحظ أنّ بانية الصنف ParagraphPage جاهزة، وتحتوي على مثال بسيط جاهز. احذف محتويات هذه البانية، لنبدأ العمل على تطبيقنا. سننشئ لصيقة Label تحتوي على مقطع نصيّ مكتوب باللغة العربية. اكتب الشيفرة التالية ضمن بانية الصنف ParagraphPage: Content = new Label { VerticalOptions = LayoutOptions.Center, Text = "تُعتبر منصّة Xamarin في الوقت الحالي، من أهمّ منصّات تطوير تطبيقات الأجهزة المحمولة المتوفّرة،" + " والتي يبدو أنّها ستبقى على الواجهة لوقت ليس بالقليل، " + "خصوصًا بعد استحواذ شركة مايكروسوفت على الشركة المنتجة لها والتي تحمل أيضًا نفس الاسم." + "سنتعلّم من خلال هذه السلسلة كتابة تطبيقات عمليّة من خلال استخدام هذه التقنيّة الواعدة." }; انتقل إلى الملف App.cs ضمن المشروع (TextManipulationApp (Portal واحرص على أن تكون بانية الصنف App على الشكل التالي: public App() { // The root page of your application MainPage = new ParagraphPage(); } نفّذ البرنامج باستخدام F5 لتحصل على شكل شبيه بما يلي: تجدر الملاحظة أنّه تتم مُحاذاة كامل النص رأسيًّا في منتصف الشاشة. سبب ذلك هو إسناد القيمة LayoutOptions.Center إلى الخاصيّة VerticalOptions (خيارات التموضع الرأسيّة) للصيقة Label. لاحظ أيضًأ أنّ النص في الشكل السابق تمّت مُحاذاته نحو اليسار، وليس إلى اليمين كما هو متوقّع بالنسبة للنصوص العربيّة. يعود سبب ذلك إلى أنّ الخاصيّة HorizontalTextAlignment من الصنف Label والمسؤولة عن المحاذاة الأفقيّة للنص ضمن اللصيقة سيكون لها القيمة الافتراضيّة TextAlignment.Start عند تشغيل البرنامج. و TextAlignment عبارة عن معدودة enum تحتوي على ثلاثة قيم هي: Start و Center و End. وبما أنّ شاشة الجهاز المحمول تُقسم إلى ثلاثة مناطق أساسيّة أفقيًا: Start للمنطقة اليسرى و Center للمنطقة الوسطى (المركزية) و End للمنطقة اليمنى من الشاشة لذلك ستتم محاذاة النص إلى اليسار بدلًا من اليمين. إذا أردنا أن تمم مُحاذاة النص إلى اليمين، فكل ما عليك فعله هو إسناد القيمة TextAlignment.End للخاصية HorizontalTextAlignment، وهذا ما سنفعله بعد قليل. وهناك أمر آخر نلاحظه أيضًا من الشكل السابق، وهو أنّ النص يقترب كثيرًا من حواف الشاشة حتى يكاد يلتصق بها. حلّ هذا الموضوع بسيط ويتمثّل في إضافة حشوة padding على حواف الشاشة، وهي عبارة عن مساحة صغيرة يمكن تعيينها برمجيًا للشاشة، نستطيع من خلالها التحكم بمساحة الهامش على الأطراف الأربعة لها. انظر الآن إلى بانية الصنف ParagraphPage بعد التعديلات المطلوبة: public ParagraphPage() { Content = new Label { VerticalOptions = LayoutOptions.Center, Text = "تُعتبر منصّة Xamarin في الوقت الحالي، من أهمّ منصّات تطوير تطبيقات الأجهزة المحمولة المتوفّرة،" + " والتي يبدو أنّها ستبقى على الواجهة لوقت ليس بالقليل، " + "خصوصًا بعد استحواذ شركة مايكروسوفت على الشركة المنتجة لها والتي تحمل أيضًا نفس الاسم." + "سنتعلّم من خلال هذه السلسلة كتابة تطبيقات عمليّة من خلال استخدام هذه التقنيّة الواعدة.", HorizontalTextAlignment = TextAlignment.End }; Padding = new Thickness(5, 5, 5, 5); } أضفنا أولًا الخاصيّة HorizontalTextAlignment إلى اللصيقة Label وأسندنا إليها القيمة TextAlignment.End لمحاذاة النص ضمن اللصيقة نحو اليمين (لاحظ أنّ الفاصلة العادية تفصل بين الخصائص). كما أضفنا الخاصية Padding (من الصنف ParagraphPage) وأسندنا إليها كائنًا جديدًا من الصنف Thickness (السماكة). تخضع بانية الصنف Thickness لزيادة التحميل حيث لها أكثر من شكل. استخدمت الشكل الأخير الذي يقبل أربعة وسائط تمثّل مقادير الحشوة padding من اليسار left والأعلى top واليمين right والأسفل bottom على الترتيب. قد تتساءل عن واحدة القياس المستخدمة هنا، ولكنني سنؤجل النقاش حولها إلى درس لاحق. عند تنفيذ البرنامج ستحصل على شكل شبيه بما يلي: لاحظ الآن كيف تمّت مُحاذاة النص نحو اليمين (ضمن اللصيقة Label)، ولاحظ أيضًا كيف ابتعد النص عن حواف الشاشة بعد تعيين الخاصية Padding لصفحة المحتوى ParagraphPage. ملاحظة: أرجو التمييز بين الخاصيتين HorizontalOptions و HorizontalTextAlignment للصيقة Label. فالأولى تمثّل خيارات التموضع الأفقية للصيقة ضمن الشاشة. أمّا الثانية فتمثّل محاذاة النص الأفقية ضمن اللصيقة. وبنفس الأسلوب، يجب التمييز بين الخاصيتين VerticalOptions و VerticalTextAlignment للصيقة Label ولكن من الناحية الرأسية بدلًا من الأفقية. تنسيق النص ضمن اللصيقة يمكن تنسيق النص الموجود ضمن لصيقة Label بالشكل الذي نرغبه عن طريق استخدام خاصيّة أخرى من خصائص اللصيقة وهي FormattedText. الخاصية FormattedText هي من الصنف FormattedString، والذي يحتوي بدوره على خاصية اسمها Spans من النوع <IList<Span فهي عبارة عن مجموعة (قائمة) من كائنات من النوع Span. أيُّ كائن من الصنف Span ينسّق جزءًا محدّدًا من النص الكلي، ويتم التحكم بهذا التنسيق من خلال ست خصائص من الصنف Span هي: جزء النص المراد تنسيقه Text واسم الخط FontFamily وحجم الخط FontSize وسمات الخط FontAttributes ولون النص ForegroundColor ولون الخلفية BackgroundColor. إذا شعرت ببعض الارتباك من الكلام السابق فلا بأس! سنوضّح هذه الأمور من خلال مثال بسيط يعمل على إضافة بعض التأثيرات على النص الذي يظهر على الشاشة. انظر أولًا إلى الشكل الذي أود الحصول عليه ثم لنوضّح كيفيّة فعل ذلك برمجيًّا: قد لا يبدو التنسيق السابق جميلًا، ولكنّه كفيل بتوضيح الفكرة. لاحظ في البداية أنّ كلمة "المجد" قد لوّنت باللون الأزرق مع خلفية صفراء. أمّا عبارة "لن تبلغ المجد حتى تلعق الصبر" فتم تلوينها باللون Aqua مع ملاحظة أنّ هذه العبارة ذات حجم نص أكبر من حجم النص للعبارة التي قبلها. لقد أضفت صنف محتوى جديد Forms ContentPage سميته FormattedParagraphPage إلى نفس المشروع (TextManipulationApp (Portable السابق، وبأسلوب مماثل للأسلوب الذي أضفنا فيه الصنف ParagraphPage في بداية هذا الدرس. انظر إلى محتويات الملف FormattedParagraphPage.cs: 1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Reflection.Emit; 5 using System.Text; 6 7 using Xamarin.Forms; 8 9 namespace TextManipulationApp 10 { 11 public class FormattedParagraphPage : ContentPage 12 { 13 public FormattedParagraphPage() 14 { 15 FormattedString formattedString = new FormattedString(); 16 17 formattedString.Spans.Add(new Span 18 { 19 Text = "لا تحسبنّ ", 20 }); 21 22 formattedString.Spans.Add(new Span 23 { 24 Text = "المجد ", 25 BackgroundColor = Color.Yellow, 26 ForegroundColor = Color.Blue, 27 }); 28 29 formattedString.Spans.Add(new Span 30 { 31 Text = "تمرًا أنت آكله ", 32 }); 33 34 formattedString.Spans.Add(new Span 35 { 36 Text = "لن تبلغ المجد حتى تلعق الصبر.", 37 ForegroundColor = Color.Aqua, 38 FontSize = Device.GetNamedSize(NamedSize.Large, typeof(Label)) 39 }); 40 41 Content = new Label 42 { 43 VerticalOptions = LayoutOptions.Center, 44 FormattedText = formattedString, 45 HorizontalTextAlignment = TextAlignment.End, 46 FontSize = Device.GetNamedSize(NamedSize.Medium, typeof(Label)) 47 }; 48 49 Padding = new Thickness(5, 5, 5, 5); 50 } 51 } 52 } بدأنا في البانية (السطر 15) بإنشاء كائن من الصنف FormattedString وإسناده إلى المتغيّر formattedString. بعد ذلك بدأنا بإضافة النصوص المنسّقة إلى القائمة Spans باعتماد الأسلوب البسيط التالي (الأسطر من 17 حتى 20): formattedString.Spans.Add(new Span { Text = "لا تحسبنّ " }); استخدمنا التابع Add من القائمة Spans لإضافة كائن من النوع Span حيث أنشأنا هذا الكائن وأسندنا الخاصية Text له مباشرة عند الإنشاء. النص في الشيفرة السابقة لا يحمل أي تنسيق خاص. في الحقيقة لقد كرّرنا هذا الأسلوب من أجل كل جزء من النص الكامل. ولكنّنا اعتمدنا بعض التنسيقات النصيّة المختلفة أحيانًا. انظر مثلًا إلى الشيفرة الموجودة في الأسطر من 22 حتى 27: formattedString.Spans.Add(new Span { Text = "المجد ", BackgroundColor = Color.Yellow, ForegroundColor = Color.Blue, }); هذه المرّة نسّقنا النص "المجد" بحيث أسندنا اللون الأصفر كلون للخلفية BackgroundColor، واللون الأزرق للون النص ForegroundColor. انظر أيضًا إلى جزء النص الذي أضفناه في الأسطر من 34 حتى 39: formattedString.Spans.Add(new Span { Text = "لن تبلغ المجد حتى تلعق الصبر.", ForegroundColor = Color.Aqua, FontSize = Device.GetNamedSize(NamedSize.Large, typeof(Label)) }); هذه المرّة سيكون لون الخط Aqua وحجم الخط كبير Large. لاحظ كيف أسندنا قيمة حجم الخط إلى الخاصية FontSize. استخدمت لهذا الغرض التعبير التالي: Device.GetNamedSize(NamedSize.Large, typeof(Label)) الصنف Device يمثّل الجهاز الحالي الذي يعمل عليه تطبيقنا، ويحتوي على العديد من التوابع الساكنة المفيدة. من هذه التوابع استخدمنا التابع GetNamedSize الذي يُرجع حجم الخط المطلوب حسب الوسائط الممرّرة له. الوسيط الأول هو NamedSize.Large أي أنّنا نريد حجم كبير للخط (NamedSize هي معدودة)، والوسيط الثاني هو نوع العنصر المراد تطبيق هذا الخط عليه. في حالتنا نريد تطبيق هذا الخط على لصيقة Label لذلك مرّرنا (typeof(Label كوسيط ثانٍ حيث يُرجع النوع الخاص Type باللصيقة Label. هذا هو الأسلوب المفضّل في تعيين القياسات المناسبة لأيّ شيء يخطر ببالك، وذلك لأنّ الأجهزة التي نعمل عليها ستكون شاشاتها مختلفة القياسات بكلّ تأكيد، لذلك فنرغب أن نترك للتابع GetNamedSize مهمة القيام بالعمليات الحسابية المناسبة لإرجاع حجم الخط المناسب بالنسبة لحجم الشاشة التي يعمل عليها البرنامج حاليًا، وللحجم المراد الحصول عليه (في مثالنا هذا أردنا الحصول على حجم خط كبير NamedSize.Large وتوجد بالتأكيد قياسات أخرى). ما تبقى من البرنامج سهل للغاية، حيث نقوم في السطر 44 بإسناد المتغيّر formattedString إلى الخاصية FormattedText للصيقة. يكفل ذلك بعرض النص منسّقًا على الشاشة، مع الانتباه إلى أنّنا لم نستخدم في هذه الحالة الخاصية Text للصيقة. هناك أمر أخير يجب الانتباه إليه. لاحظ أنّنا في السطر 46 نُسند حجم خط جديد إلى الخاصية FontSize الخاصة باللصيقة. سيكون هذا الخط متوسّط الحجم (لاحظ الوسيط الأوّل NamedSize.Medium). الفكرة هنا هو أنّ هذا الخط سيطبّق على كل جزء من النص (موجود ضمن كائن Span) في حال لم تُعيّن الخاصية FontSize الخاصة بهذا الجزء. فمثلًا لن يطبّق هذا الحجم على جزء النص المعيّن في الأسطر بين 34 و 39. وذلك لأنّ هذا الجزء قد عيّن حجم خط خاص به (NamedSize.Large) كما رأينا قبل قليل. ملاحظة: ستحتاج لتجربة هذا البرنامج إلى تعديل بسيط ضمن بانية الصنف App ضمن الملف App.cs. احرص على أن تكون البانية على الشكل التالي لتستخدم صنفنا الجديد FormattedParagraphPage: public App() { // The root page of your application MainPage = new FormattedParagraphPage(); } الخلاصة تعرّفنا في هذا الدرس على مبادئ التعامل مع النصوص في Xamarin وبنينا للمرّة الأولى تطبيقين بسيطين يعرضان بعض المعلومات على المستخدم. تعلّمنا كيفيّة محاذاة النصوص وإجراء بعض عمليّات التنسيق عليها مثل تغيير لون النص ولون الخلفية لأيّ كلمة أو عبارة. لم يتناول هذا الدرس كيفيّة معالجة الحالة التي يكون فيها النص كبيرًا ويحتاج إلى وسيلة لتمريره لعرض محتوياته كاملة. سنعالج هذه المسألة في درس لاحق.
  12. لا تخلو أيّ لغة برمجة محترمة من وسيلة لمعالجة الأخطاء. تحتوي لغة سي شارب على آليّة قويّة لمعالجة الأخطاء. تشبه هذه الآلية إلى حدّ ما تلك المستخدمة في لغة Java. هناك نوعان أساسيّان من الأخطاء التي قد تنتج عن البرنامج: النوع الأوّل هي أخطاء أثناء التصميم، أي أثناء كتابة الشيفرة، وهي أخطاء يمكن اكتشافها بسهولة من خلال مترجم سي شارب الذي يتعرّف عليها ويعطيك وصفًا عنها، وقد يزوّدك أيضًا ببعض المقترحات للتخلّص منها. بالنسبة للنوع الثاني من الأخطاء فهي التي تنتج أثناء تنفيذ البرنامج runtime errors. توجد الكثير من الأسباب لحدوث مثل هذه الأخطاء. فمن القسمة على صفر إلى القراءة من ملف غير موجود أو حتى استخدام متغيّر مصرّح على أنّه من نوع مرجعيّ ولكن لم تتم تهيئته بعد بمرجع إلى كائن. عند حدوث مثل هذه الأخطاء ترمي بيئة التنفيذ المشتركة CLR استثناءً exception يحتوي على معلومات بخصوص الخطأ الذي حدث، فإذا كنت مستعدًّا لالتقاط هذا الاستثناء فستنجو، وإلّا سيتوقّف برنامجك عن العمل بصورة مفاجئة. التقاط استثناء من خلال عبارة try-catch إذا صادفتك عبارة برمجيّة تحتوي على عمليّة حسابيّة أو على استدعاء لتابع آخر، أو أيّ شيء قد يثير الريبة في نفسك، فمن الممكن مراقبتها أثناء تنفيذ البرنامج باستخدام عبارة try-catch. تتألّف هذه العبارة من قسمين: القسم الأوّل هو قسم المراقبة try، والقسم الثاني هو قسم الالتقاط catch. الشكل "الأبسط" لهذه العبارة هو التالي: try { //عبارة برمجيّة مريبة } catch(Exception exp) { //هنا يُلتقط الاستثناء وتتمّ معالجته } يمكن أن يحوي قسم try على عبارات برمجيّة بقدر ما ترغب. عندما يصادف البرنامج أثناء التنفيذ خطأً ما، سيتوقّف التنفيذ عند العبارة التي سبّبت الخطأ، ثم ينتقل فورًا إلى قسم الالتقاط catch. لاحظ معي أنّ قسم catch سيُمرَّر إليه وسيط من الصنف Exception. الصنف Exception موجود ضمن نطاق الاسم System، وهو الصنف الأب لجميع الاستثناءات. إذ أنّ أي استثناء مهما كانت صفته (بما فيها الاستثناءات التي يمكنك أن تكتبها أنت) يجب أن ترث من هذا الصنف. بعد حدوث الاستثناء والانتقال إلى قسم catch، سيحتوي الوسيط exp على معلومات حول مكان حدوث الاستثناء وسبب حدوثه، وغيرها من المعلومات التي قد تكون مفيدة لمستخدم البرنامج. تجدر الملاحظة بأنّ البرنامج لن يدخل إلى القسم catch أبدًا ما لم يحدث استثناء ضمن القسم try. لنرى الآن البرنامج Lesson16_01 الذي سيعرّفنا على الاستثناءات بشكل عمليّ. يحتوي هذا البرنامج البسيط على عبارة try-catch وحيدة سنعمل من خلالها على توليد خطأ بشكل مقصود أثناء التنفيذ، وسنتعلّم كيفيّة المعالجة. 1 using System; 2 3 namespace Lesson16_01 4 { 5 class Program 6 { 7 static void Main(string[] args) 8 { 9 int x = 5; 10 int y = 0; 11 int result; 12 13 try 14 { 15 result = x / y; 16 } 17 catch(Exception exp) 18 { 19 Console.WriteLine("The following error has occurred:"); 20 Console.WriteLine(exp.Message); 21 } 22 23 Console.WriteLine("Good Bye!"); 24 } 25 } 26 } من الواضح أنّ هذا البرنامج يتجّه لأن يجري عمليّة قسمة على صفر في السطر 15 ضمن القسم try. عند وصول البرنامج إلى السطر 15 وإجراء عمليّة القسمة هذه، سيتولّد استثناء يؤدّي إلى انتقال التنفيذ مباشرةً إلى قسم catch في السطر 17. في قسم catch يعرض البرنامج معلومات عن هذا الخطأ باستخدام الخاصيّة Message لكائن الحدث exp. في النهاية يعرض البرنامج في السطر 23 رسالة توديعيّة للمستخدم. نفّذ البرنامج لتحصل على الخرج التالي: The following error has occurred: Attempted to divide by zero. Good Bye! جرّب الآن تغيير قيمة المتغيّر y لتصبح 1 مثلًا وأعد تنفيذ البرنامج. ستلاحظ ظهور الرسالة التوديعيّة فقط على الشاشة، أي أنّه لم يحدث أي استثناء هذه المرّة. على كلّ الأحوال لا ينصح باستخدام معالجة الاستثناءات من أجل حالة القسمة على صفر في البرنامج السابق. ملاحظة: في الواقع يمكن الاستغناء عن الوسيط الذي يمرّر إلى قسم catch بشكل كامل، وفي هذه الحالة لن يكون بإمكانك الحصول على معلومات حول الاستثناء المُلتقط. سيبدو شكل عبارة try-catch على الشكل التالي: try { //عبارة برمجيّة مريبة } catch { //هنا يُلتقط الاستثناء وتتمّ معالجته } من الممكن استخدام هذا الأسلوب إذا كنّا على يقين حول طبيعة الخطأ الذي سيحدث. عبارة try-catch أكثر تطورا تصادفنا في بعض الأحيان حالات يكون من الضروري معها مراقبة أكثر من عبارة برمجيّة مريبة ضمن قسم try. لقد اتفقنا أنّه عند حدوث أيّ استثناء ضمن قسم try سينتقل التنفيذ إلى قسم catch. ولكن كيف سنميّز العبارة التي سبّبت هذا الاستثناء في قسم try؟ توجد العديد من الأصناف التي ترث من الصنف Exception والتي يُعتبر كلّ منها استثناءً مخصّصًا أكثر للمشكلة التي قد تحدث. فمثًلا كان من الممكن في البرنامج Lesson16_01 السابق أن نستخدم الصنف DivideByZeroException بدلًا من الصنف Exception في عبارة catch، وذلك لأنّه يرث (بشكل غير مباشر) من الصنف Exception، وسيعمل البرنامج كما هو متوقّع. ولكن في هذه الحالة لن تستطيع catch التقاط سوى الاستثناءات التي تنتج عن القسمة على صفر. في كثير من الحالات قد تتسبّب العبارات البرمجيّة الموجودة في قسم try باستثناءات متنوّعة لا توجد علاقة فيما بينها. مما يفرض علينا استخدام الصنف Exception لكي نلتقط بشكل مؤكّد أي استثناء قد يصدر عنها، أو أن تساعدنا سي شارب في هذا الخصوص! في الحقيقة الخيار الثاني هو الأفضل وهو جاهز. يمكننا في الواقع إضافة أقسام catch أخرى بقدر ما نرغب بعد قسم try. سيوضّح البرنامج Lesson16_02 هذه الفكرة من خلال فتح ملف نصي ومحاولة قراءة محتوياته. لاحظ أنّنا سنستخدم هنا نطاق الاسم System.IO. 1 using System; 2 using System.IO; 3 4 namespace Lesson16_02 5 { 6 class Program 7 { 8 static void Main(string[] args) 9 { 10 string contents; 11 12 Try 13 { 14 using (StreamReader sr = new StreamReader("myfile.txt")) 15 { 16 contents = sr.ReadLine(); 17 } 18 19 Console.WriteLine("This file contains {0} characters.", contents.Length); 20 } 21 catch(NullReferenceException nullExp) 22 { 23 Console.WriteLine("The file does not contain any data."); 24 } 25 catch(FileNotFoundException notFoundExp) 26 { 27 Console.WriteLine("File: {0} not found!", notFoundExp.FileName); 28 } 29 } 30 } 31 } يستخدم هذا البرنامج قسميّ catch. القسم الأوّل (الأسطر من 21 إلى 24) يلتقط استثناءً من النوع NullReferenceException وهذا يحدث عند محاولة استدعاء تابع أو خاصيّة من متغيّر يحتوي على null بدلًا من مرجع لكائن حقيقي. أمّا القسم الثاني (الأسطر من 25 إلى 28) فهو يلتقط استثناءً من النوع FileNotFoundException والذي يحدث عند محاولة القراءة من ملف غير موجود. نفّذ البرنامج السابق وستحصل على الرسالة التالية في الخرج: File: C:\\Users\Husam\documents\visual studio 2015\Projects\Lesson16_02\Lesson16_02\bin\Debug\myfile.txt not found! وهذا طبيعي تمامًا لأنّني لم أنشئ الملف myFile.txt في هذا المسار. لاحظ كيف يضيف الصنف FileNotFoundException خاصيّة جديدة له وهي FileName (السطر 27) من النوع string التي تحوي مسار الملف مع اسمه. أنشئ الآن الملف myFile.txt واتركه فارغًا، ثمّ ضعه ضمن نفس المجلّد الذي يحوي الملف التنفيذي للبرنامج (موجود ضمن bin\Debug\). أعد تنفيذ البرنامج وستحصل على الرسالة التالي: The file does not contain any data. السبب في ظهور هذه الرسالة هو الاستثناء NullReferenceException وذلك لأنّنا حاولنا الوصول إلى الخاصيّة Length (تعطينا عدد المحارف الموجودة ضمن متغيّر نصي) من المتغيّر النصي contents رغم أنّه يحتوي على null (تذكّر بأنّنا تركنا الملف myFile.txt فارغًا). اذهب إلى الملف myFile.txt الذي أنشأناه قبل قليل، واكتب بعض الكلمات ضمنه واحفظ الملف، ثمّ أعد تنفيذ البرنامج Lesson16_02 من جديد. يجب الآن أن تحصل على رسالة تخبرك بعدد الأحرف التي كتبتها ضمن الملف. ملاحظة: يجب الانتباه إلى ترتيب أقسام catch. فلو كان مثلًا أوّل قسم catch موجود بعد قسم try يلتقط استثناءً من النوع Exception فعندها لن يستطيع أي قسم لاحق التقاط أي استثناء، لأنّ جميع الاستثناءات سيلتقطها هذا القسم الأوّل. السبب في ذلك أنّ الصنف Exception هو الأب العام لجميع أصناف الاستثناءات الأخرى، فيمكن له التقاطها. عبارة try-catch-final يمكن إضافة قسم أخير لعبارة try-catch اسمه final. وكما يوحي اسمه، فهذا القسم يمكن له أن يحتوي على عبارات برمجيّة سيتمّ تنفيذها بعد أن يدخل البرنامج إلى القسم try العائد له. وذلك سواءً أحدث استثناء ضمن try أم لم يحدث. تكمن فائدة وجود هذا القسم، في أنّه قد نواجه أحيانًا بعض الحالات التي تتطلّب إجراء بعض المهام عندما نفرغ من قسم try مثل إغلاق بعض المصادر المفتوحة، أو تحرير الذاكرة بشكل فوري وغيرها. الشرط الوحيد لاستخدام هذا القسم الاختياري هو أن يكون آخر قسم في عبارة try-catch. سنعدّل البرنامج Lesson16_02 ليدعم القسم final. انظر البرنامج Lesson16_03. 1 using System; 2 using System.IO; 3 4 namespace Lesson16_03 5 { 6 class Program 7 { 8 static void Main(string[] args) 9 { 10 string contents; 11 12 try 13 { 14 using (StreamReader sr = new StreamReader("myfile.txt")) 15 { 16 contents = sr.ReadLine(); 17 } 18 Console.WriteLine("This file contains {0} characters.", contents.Length); 19 } 20 catch(NullReferenceException nullExp) 21 { 22 Console.WriteLine("The file does not contain any data."); 23 } 24 catch(FileNotFoundException notFoundExp) 25 { 26 Console.WriteLine("File: {0} not found!", notFoundExp.FileName); 27 } 28 finally 29 { 30 Console.WriteLine("Good Bye!"); 31 } 32 } 33 } 34 } سواءً كان الملف myFile.txt موجودًا أم غير موجود، أو كان يحتوي على بيانات أم فارغاً، ستظهر العبارة !Good Bye على الشاشة. ملاحظة: من الأفضل دومًا أن تحاول عدم استخدام عبارة try-catch وأن تستخدم عبارة if لاختبار الحالات التي تواجهك قبل تنفيذها. استخدم try-catch إذا كان ذلك ضروريًّا. والسبب في ذلك أنّ عمليّة معالجة الأخطاء بشكل عام تتطلّب المزيد من الموارد المخصّصة للبرنامج، مما قد يؤثّر على أداء البرنامج في حال تمّ استخدامها بشكل غير مدروس. تمارين داعمة تمرين 1 عدّل البرنامج Lesson16_02 بحيث يمكن الاستغناء عن عبارة try-catch تمامًا. (تلميح: ستحتاج إلى استخدام عبارتيّ if في هذه الحالة). تمرين 2 لتكن لدينا الشيفرة التالية: string input = Console.ReadLine(); int t = int.Parse(input); تطلب الشيفرة السابقة من المستخدم أن يدخل عددًا على شكل نص لتعمل على تحويله إلى قيمة عدديّة باستخدام التابع int.Parse. المطلوب هو إضافة عبارة try-catch إلى الشيفرة السابقة لمعالجة استثناء ممكن الحدوث في حال أدخل المستخدم قيمة مثل "u" وهي لا يمكن تحويلها إلى قيمة عدديّة كما هو واضح. (تلميح: استخدام الاستثناء FormatException الذي يُعبّر عن هذه الحالة). الخلاصة تعرّفنا في هذا الدرس على كيفيّة معالجة الأخطاء التي تظهر أثناء تنفيذ البرنامج. في الحقيقة هذا الموضوع ذو شجون! وهناك الكثير ليقال، ولكن يكفي الآن أن تتعرّف على المبادئ الأساسيّة لالتقاط الاستثناءات، وكيفيّة معالجتها. يمكنك استخدام هذا الأسلوب في جميع التطبيقات التي تُنشئها باستخدام سي شارب، مثل تطبيقات الويب بأنواعها، وتطبيقات سطح المكتب، وحتى تطبيقات الأجهزة الذكيّة باستخدام تقنيّة Xamarin.
  13. أنصحك بإطار عمل Lavarel فهي تدعم MVC وأيضًا يمكنك استخدام تقنيّة DI معها، وهي تدعم أيضًا الـ Authorization (انظر هنا). كما أنّها تمتلك بيئة تطوير تجريبيّة توفّر لك جميع الأدوات التطويريّة اللازمة للبدء بالبرمجة من خلال Virtual Machine مُعدّ لهذا الغرض (انظر هنا) توجد مصادر كثيرة للتعلّم ولكنّ المشكلة في أنّه ينبغي عليك أن تكون ملمًّا أصلًا في PHP كي تبدأ بـ Lavarel. انظر المصادر المجّانيّة التالية للتعلّم: http://learninglaravel.net/ https://laracasts.com/series https://laracasts.com/series/laravel-5-fundamentals https://belitsoft.com/laravel-development-services/laravel-5-tutorial
  14. مبدأ آخر من مبادئ التصميم الكائنيّ التوجّه ضمن مبادئ SOLID يُطلق عليه اسم مبدأ ليسكوف للاستبدال Liskov Substitution Principle ويُرمز له اختصارًا بالرمز LSP. سنبدأ هذا المبدأ بشيء من المفاهيم النظريّة. صاحبة هذا المبدأ هي البروفسور باربارا ليسكوف، وقد طرحته أوّل الأمر عام 1987 وكان ينص على ما يلي: قد يبدو الكلام السابق مبهمًا بعض الشيء، يمكننا توضيحه بالشكل التالي: "إذا استطعنا استبدال كل كائن O1 مكان كائن O2 فمن الممكن الجزم بأنّ S هو نوع فرعي (نوع ابن) للنوع T". ولكن في بعض الأحيان رغم أنّ S هو نوع فرعي للنوع T ولكن لا يمكن الاستبدال بين كائناتهما بالصورة الموضّحة قبل قليل وهذا بالطبع أمر غير جيّد. أعادت باربارا ليسكوف بالاشتراك مع جانيت وينغ Jeannette Wing صياغة المبدأ السابق بشكل مختصر أكثر في عام 1994 ليصبح على الشكل التالي: المقصود هنا أنّه إذا كانت خاصيّة أو دالّة (طريقة method) تعمل ضمن كائنات من النوع T، فينبغي أن تعمل أيضًا وبنفس الصورة ضمن كائنات من النوع S بحيث أنّ النوع S هو نوع ابن للنوع T. وهذا هو مبدأ ليسكوف للاستبدال الذي وصفه روبرت مارتن لاحقًا بصورة أكثر عمليّةً في إحدى مقالاته على الشكل التالي: بعد كلّ هذه المقدّمة النظريّة ثقيلة الظلّ بعض الشيء، ينبغي أن نُدعّم المفاهيم النظريّة السابقة ببعض الأمثلة التي توضّحها بالشكل المناسب. واحد من هذه الأمثلة التي تخرق هذا المبدأ هو تمثيل العلاقة بين المستطيل والمربّع بشكل كائنيّ. من البديهي تمامًا أن يكون لدينا صنف اسمه Square (مربّع) يكون صنفًا ابنًا للصنف Rectangle (مستطيل)، فمن الناحية الرياضيّة يُعتبر المربّع حالة خاصّة من المستطيل، فهو مستطيل تساوى فيه بعداه. لنعبّر في البداية عن الصنف Rectangle برمجيًّا بالشكل التالي: class Rectangle { int width; int height; public: int getWidth() { return width; } int getHeight() { return height; } virtual void setWidth(int value) { width = value; } virtual void setHeight(int value) { height = value; } }; وبما أنّ المربّع هو حالة خاصّة من المستطيل كما أسلفنا، فيمكن كتابة الصنف Square مع إعادة تعريف الطريقتين setWidth و setHeight: class Square : public Rectangle { public: void setWidth(int value) { width = value; height = value; } void setHeight(int value) { width = value; height = value; } }; سيضمن هذا التعديل على الطريقتين setWidth و setHeight ضمن الصنف Square أنّ أي كائن (مربّع) ننشئه من الصنف Square ستكون أضلاعه الأربعة متساوية الطول. لننظر الآن إلى الدّالة التالية التي سنستخدمها لتجريب البنية الكائنيّة السابقة: bool test(Rectangle &rectangle) { rectangle.setWidth(2); rectangle.setHeight(3); return rectangle.getWidth() * rectangle.getHeight() == 6; } تقبل الدّالة test تمرير كائن من النوع Rectangle أو كائن من النوع Square، وهذا جائز بالطبع لأنّ Square هو نوع ابن للنوع Rectangle. السؤال هنا هو ماذا سيحدث عند تمرير كائن من النوع Square إلى الدّالة test؟ ستُعيد الدّالة القيمة false رغم أنّ التقييم يجري على مرجع من النوع Rectangle (وسيط الدّالة). المشكلة هنا أنّه رغم أنّ المربّع هو مستطيل من الناحية الرياضيّة إلّا أنّه لا يتشارك السلوك نفسه معه. وهذا يُعتبر خرقًا واضحاً لمبدأ الاستبدال. فالكائنات من النوع Rectangle لا يمكن أن يتمّ استبدالها بكائنات من النوع Square رغم أنّ النوع Square هو نوع ابن للنوع Rectangle، لأنّ ذلك سيؤدّي إلى تغيّر في سلوك الطرائق الموجودة ضمن الصنف Rectangle كما هو واضح. لقد أوضح برتراند ماير هذه المسألة أيضًا على الشكل التالي: الإجراء من النص السابق قد يكون دالة أو طريقة method. الشروط البادئة هي الشروط التي يجب أن تتحقّق كي يتم استدعاء الطريقة وتنفيذها، أمّا الشروط اللّاحقة فهي محقّقة دومًا بعد انتهاء تنفيذ الطريقة. فالّذي قصده برتراند ماير هو أنّه عند إعادة كتابة تابع في صنف ابن يرث من صنف أب، يجب ألّا تكون الشروط البادئة أقوى من الشروط البادئة للطريقة الأصليّة (الموجودة في النوع الأب)، كما يجب ألّا تكون الشروط الّلاحقة أضعف من الشروط الّلاحقة للطريقة الأصليّة. في مسألتنا السابقة (مسألة المربّع والمستطيل)، لم تكن هناك أيّ شروط بادئة، ولكن كان هناك شرط لاحق للطريقة setHeight وهو أنّ هذه الطريقة يجب ألّا تُغيّر العرض width، وهذا الشرط تمّ خرقه (أصبح أضعف) عندما أعدنا تعريف الطريقة setHeight ضمن النوع Square. تُعتبر الوراثة في الحقيقة مفهومًا قويًّا ومحوريًّا في التصميم كائنيّ التوجّه، ولكن من السهل الوقوع في الأخطاء إذا لم نطبّق مبدأ ليسكوف للاستبدال، فقد تجد أنّ تصميمك الكائنيّ جيّد ومقنع، ولكن سرعان ما ستقع في المتاعب إذا لم تراعي هذا المبدأ. ترجمة -وبتصرّف- للمقال Liskov Substitution Principle لصاحبه Radek Pazdera.
  15. تعتبر تقنيّة Linq من التقنيّات الجديدة نسبيًّا في سي شارب ولغات دوت نت عمومًا. تسمح هذه التقنيّة بإجراء عمليّات استعلام معقّدة لاستخلاص البيانات بشكل سلس وسهل بسبب شكلها المألوف كما سنرى لاحقًا في هذا الدرس. لتقنيّة Linq أشكال متعدّدة: Linq to Objects: للتعامل مع البيانات الموجودة في ذاكرة البرنامج. Linq To XML: للتعامل مع البيانات النصيّة الموجودة بتنسيق XML. Linq To SQL: وهي تقنيّة خاصّة بالحصول على البيانات من خادم SQL Server. في الحقيقة تمّ التخلّي عن هذه التقنيّة رغم حداثتها، وذلك لصالح تقنيّة أحدث وأكثر تطوّرًا وهي Entity Framework. كما يتحدّث هذا الدرس عن تعابير Lambda وهي من المزايا المفيدة والتي تسهّل عمل المبرمجين إلى حدٍّ كبير. ستناول في هذا الدرس الشكل الأوّل من Linq، وهو استخدام Linq مع الكائنات Objects. ولكن قبل ذلك لنتحدّث قليلًا عن تعابير Lambda. تعابير Lambda تستطيع تخيّل تعابير Lambda على أنّها دوال functions صغيرة ليس لها اسم، تعمل على إجراء عمليّات حسابيّة بسيطة، ومن الممكن أن ترجع نتيجة. في الواقع يمكن للنوّاب Delegates أن تغلّف تعابير Lambda ضمن شروط محدّدة. سنتناول مثالًا بسيطًا يوضّح كيفيّة التعامل معها. انظر إلى الشيفرة التالية: class Program { delegate int Square(int x); static void Main(string[] args) { Square square = (x) => x * x; int result = square(5); } } صرّحنا في الشيفرة السابقة عن النائب Square الذي يتطلّب وسيطًا واحدًا من النوع int ويُرجع قيمة من نفس النوع. يُفترض بهذا النائب بأن يُغلّف التوابع التي تعمل على إيجاد مربّع عدد صحيح. إذا نظرت الآن إلى التابع Main ستجد أنّنا في السطر الأوّل منه نصرّح عن المتغيّر square من نوع النائب Square، حيث نُسند إليه ما يلي: (x) => x * x التعبير السابق هو تعبير Lambda بسبب وجود السهم <= ضمنه. فهم هذا التعبير بسيط، فهو يطلب وسيطًا وحيدًا (x) على يسار السهم، ويضرب قيمة هذا الوسيط بنفسها: x * x على يمين السهم، سيُرجع هذا التعبير قيمة x مضروبةً بنفسها. ولكن الملفت في الأمر أنّنا قد أسندنا هذا التعبير إلى متغيّر من نوع النائب Square. السبب في ذلك أنّ تعبير Lambda السابق يتوافق مع النائب Square في أنّه يحتاج إلى وسيط وحيد من النوع int ويُرجع قيمة من نفس النوع. ولكنّنا لم نوضّح في تعبير Lambda نوع الوسيط أو نوع القيمة المُعادة! لا مشكلة في ذلك، فسيتم استخلاص النوع من الوسيط المُمرّر وذلك بشكل تلقائي. كمثال آخر على استخدام تعبير Lambda يمكن كتابة تعبير يتطلّب وسيطين ولكن لا يُرجع أي قيمة. انظر إلى الشيفرة التالية: class Program { delegate void SumAndPrinting(int a, int b); static void Main(string[] args) { SumAndPrinting sumAndPrnt = (a, b) => Console.WriteLine(a + b); sumAndPrnt(3, 4); } } النائب SumAndPrinting يقبل الآن وسيطين من النوع int لكنّه لا يُرجع أي قيمة (void). انظر إلى تعبير Lambda كيف أصبح: (a, b) => Console.WriteLine(a + b) لاحظ كيف أنّ الوسائط الموجودة بين قوسين تفصل بينها فواصل عاديّة دون تحديد الأنواع. كما يمكنك أن تلاحظ أيضًا بأنّ هذا التعبير مهمّته جمع قيمتي الوسيطين والطبع إلى الشاشة دون إرجاع أيّ قيمة. ملاحظة: يمكننا الاستغناء عن القوسين في تعبير Lambda، إذا أردنا تمرير وسيط واحد فقط. مثل: x => x * x استعلاماتLinq للاستفادة من Linq يجب إضافة نطاق الاسم System.Linq باستخدام الكلمة المحجوزة using. أفضل وسيلة لفهم Linq هي من خلال مثال تطبيقي بسيط، في البرنامج Lesson15_01 سنستخدم الصنف Student الذي استخدمناه في درس سابق ولكن سنجري فيه بعض التعديلات البسيطة، حيث أصبحنا نفصل اسم الطالب FirstName عن كنيته LastName، بالإضافة إلى إضافة حقل جديد اسمه Id من النوع int، والذي يُعبّر عن رقم الطالب: class Student { public int Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public int Mark { get; set; } } سننشئ 10 كائنات من الصنف Student ونخزّنها ضمن مجموعة عموميّة <List<Student ثم نُجري على هذه المجموعة بعض "الحيل" باستخدام Linq. الهدف من هذا البرنامج هو إجراء عمليّة تصفية على هؤلاء الطلّاب بحيث نحصل على الطلّاب الذين تكون درجاتهم أكبر تمامًا من 60. سيحتوي البرنامج Lesson15_01 على أفكار جديدة ولكن مفيدة فكن مستعدًّا: 1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 5 namespace Lesson15_01 6 { 7 class Student 8 { 9 public int Id { get; set; } 10 public string FirstName { get; set; } 11 public string LastName { get; set; } 12 public int Mark { get; set; } 13 } 14 15 class Program 16 { 17 static void Main(string[] args) 18 { 19 List<Student> studentsList = new List<Student>() 20 { 21 new Student {Id = 1, FirstName = "Ahmad", LastName = "Morad" , Mark = 80}, 22 new Student {Id = 2, FirstName = "Husam", LastName = "Sayed" , Mark = 75}, 23 new Student {Id = 3, FirstName = "Nour", LastName = "Hasan" , Mark = 65}, 24 new Student {Id = 4, FirstName = "Bssel", LastName = "Shamma" , Mark = 30}, 25 new Student {Id = 5, FirstName = "Ahmad", LastName = "Khatib" , Mark = 90}, 26 new Student {Id = 6, FirstName = "Maryam", LastName = "Burhan" , Mark = 95}, 27 new Student {Id = 7, FirstName = "Sarah", LastName = "Burhan" , Mark = 100}, 28 new Student {Id = 8, FirstName = "Mansour", LastName = "Khalid" , Mark = 50}, 29 new Student {Id = 9, FirstName = "Omran", LastName = "Barrak" , Mark = 45}, 30 new Student {Id = 10, FirstName = "Hasan", LastName = "Anis" , Mark = 56}, 31 }; 32 33 Console.WriteLine("Full List:"); 34 Console.WriteLine("----------"); 35 PrintList(studentsList); 36 37 38 IEnumerable<Student> students = from student in studentsList 39 where student.Mark > 60 40 select student; 41 42 Console.WriteLine(); 43 Console.WriteLine("After applying Linq:"); 44 Console.WriteLine("----------"); 45 PrintList(students); 46 } 47 48 private static void PrintList(IEnumerable<Student> students) 49 { 50 Console.WriteLine("{0,-5}{1,-15}{2,-15}{3,-10}", "Id", "First Name", "Last Name", "Mark"); 51 52 foreach (Student s in students) 53 { 54 Console.WriteLine("{0,-5}{1,-15}{2,-15}{3,-10}", s.Id, s.FirstName, s.LastName, s.Mark); 55 } 56 } 57 } 58 } نفّذ البرنامج السابق لتحصل على شكل شبيه بما يلي: لاحظ كيف استثنى البرنامج في القائمة الثانية الطلاب الذين تقل درجاتهم عن 60 أو تساويها. قارن بين القائمتين واكتشف العناصر المستثناة. يبدأ البرنامج في التابع Main بإنشاء 10 كائنات من النوع Student تمثّل بيانات عشرة طلّاب وإسناد هذه الكائنات فورًا إلى المجموعة القائمة studentsList بشكل مختصر (الأسطر من 19 إلى 31). الملفت هنا هو طريقة إنشاء كل من هذه الكائنات. انظر إلى السطر 21 مثلًا: new Student {Id = 1, FirstName = "Ahmad", LastName = "Morad" , Mark = 80} هذا شكل مختصر لإنشاء كائن من النوع Student حيث استخدمنا حاضنة {} بعد اسم الصنف Student مباشرةً وكتبنا أسماء الخصائص التي نريد تهيئتها ضمن هذه الحاضنة. بالنسبة لبانيّة الصنف Student فستُستدعى بكلّ تأكيد. يعتبر هذا الشكل من الإنشاء والإسناد المباشر للخصائص مفيدًا للغاية (أستخدمه بكثرة في برامجي الخاصّة) حيث يقلّل من أسطر الشيفرة البرمجيّة إلى حدٍّ كبير. سيطبع البرنامج بعد ذلك القائمة التي أنشأناها قبل قليل من باب التوضيح، وذلك من خلال استدعاء التابع الساكن PrintList (السطر 35) بالشكل التالي: PrintList(studentsList); مرّرنا لهذا التابع القائمة الكاملة studentsList. التصريح عن التابع الساكن موجود في الأسطر بين 48 و 56 وسنتكلّم عنه بعد قليل. يحتوي السطر 38 على عبارة برمجيّة تستخدم استعلام Linq: IEnumerable<Student> students = from student in studentsList where student.Mark > 60 select student; يقع استعلام Linq على يمين عامل الإسناد (=)، وفي الحقيقة إذا كان لديك اطّلاع على لغة SQL فسيكون هذا الاستعلام مألوفًا بالنسبة إليك. لنركّز الآن على هذا الاستعلام فحسب: from student in studentsList where student.Mark > 60 select student يبدأ الاستعلام بالكلمة المحجوزة from يتبعه اسم متغيّر جديد يمكنك تسميّته بأيّ اسم ترغبه. اخترت الاسم student لأنّني وجدتّه معبّرًا. بعد اسم المتغيّر الجديد نجد الكلمة المحجوزة in وبعدها اسم المجموعة التي نريد تطبيق الاستعلام عليها. إذًا أصبح بإمكاننا قراءة السطر الأوّل من الاستعلام على الشكل التالي: يبدأ السطر الثاني بالكلمة المحجوزة where وهي اختياريّة ومن الممكن عدم كتابتها، وهي تسمح بكتابة شرط من ممكن تطبيقه على عناصر المجموعة studentsList. بالنسبة لمثالنا هذا، اخترت تطبيق الشرط: where student.Mark > 60 أي أنّني أريد أن تكون درجة كل طالب (student) أكبر تمامًا من 60. أمّا السطر الثالث select student فهو يخبر Linq عن شكل البيانات التي نريد الحصول عليها بنتيجة تنفيذ الاستعلام. في مثالنا هذا نريد الحصول على مجموعة كل عنصر من عناصرها هو كائن من النوع Student. بنتيجة تنفيذ الاستعلام السابق سيحتوي المتغيّر students على مرجع لكائن مجموعة يحقّق الواجهة <IEnumerable<Student ولا يهمّك في الحقيقة ما هو النوع الفعليّ لهذه المجموعة. يمكن لاستعلام Linq أن يُنتج مرجعًا لكائن مجموعة يحقّق الواجهة <IQueryable<Student ولكنّ الحديث عن هذا الموضوع هو خارج مجال الدرس. في الواقع يمكن استخدام شروط أكثر تعقيدًا كأن نرغب بالحصول على جميع الطلّاب الذين تتراوح درجاتهم بين 60 و90 ضمنًا على سبيل المثال، وذلك باستخدام العامل && بالشكل التالي: from student in studentsList where student.Mark >= 60 && student.Mark <= 90 select student كما من الممكن أنّ نرتّب البيانات حسب رقم الطالب id، أو بحسب اسمه FirstName أو كنيته LastName أو بمزيج منها، وذلك باستخدام الكلمة المحجوزة orderby الخاصّة بـ Linq: from student in studentsList where student.Mark >= 60 && student.Mark <= 90 orderby student.FirstName, student.LastName select student سيقوم الاستعلام السابق بترتيب العناصر التي توافق الشرط الموجود في القسم where حسب الاسم ثمّ حسب الكنيّة. توجد في الحقيقة الكثير من المزايا القويّة التي تتمتّع بها استعلامات Linq والتي لا يتّسع هذا الدرس لذكرها. بالنسبة للتابع PrintList (الأسطر من 50 حتى 58) فيقتصر دوره على طباعة جدول للقائمة التي نمرّرها كوسيط إليه. لاحظ أنّ الوسيط الوحيد الذي يقبله يحقّق الواجهة <IEnumerable<Student لذلك فيمكننا تمرير أي وسيط إليه يحمل مرجعًا إلى كائن من أيّ صنف يحقّق هذه الواجهة بما فيه بالطبع الصنف <List<Student. الأمر الوحيد الجديد في هذا التابع هو استخدامه لتنسيق مختلف في إظهار البيانات بشكل جدوليّ. انظر السطر 52. ستجد النص التنسيقي: "{0,-5}{1,-15}{2,-15}{3,-10}" يسمح هذا النص التنسيقي بعرض البيانات بشكل جدوليّ أنيق على الشاشة، حيث يسمح التنسيق التالي {0, -5} بعرض الوسيط ذو الموقع 0 (من التابع WriteLine) ضمن حقل عرضه 5 فراغات بحيث تكون المحاذاة نحو اليسار. أمّا التنسيق {1, -15} فيسمح بعرض الوسيط ذو الموقع 1 ضمن حقل عرضه 15 فراغ بحيث تكون المحاذاة نحو اليسار أيضًا. بإزالة إشارة السالب (-) من التنسيقين السابقين ستصبح المحاذاة نحو اليمين. هل تريد المزيد من الإثارة؟ أضف السطر التالي إلى السطر 43 من البرنامج السابق (أي بعد العبارة التي تستخدم استعلام Linq): double average = students.Average(s => s.Mark); تستخدم هذه العبارة التابع Average من المتغيّر students الذي يحتوي على قائمة الطلّاب بعد التصفيّة كما نعلم. وكما يُوحي اسمه يعمل هذا التابع على حساب معدّل الطلاب (كائنات Student) الموجودين ضمن students. ولكن كيف سيعرف التابع Average الحقل الذي سيتمّ بموجبه حساب المعدّل؟ يتمثّل الحل في استخدام تعبير Lambda يتطلّب وسيطًا واحدًا (الوسيط s) الذي سيمثّل كائن Student، ويُرجع قيمة الخاصيّة Mark له: s => s.Mark سيستخدم التابع هذا التعبير للمرور على جميع العناصر الموجودة ضمن المجموعة students ليحصل على درجة كلّ منها باستخدام تعبير Lambda السابق ثمّ يحسب المعدّل، ليعمل البرنامج على إسناده إلى المتغيّر average وهو من النوع double كما هو واضح. بعد تنفيذ العبارة السابقة وعلى فرض أنّ نسخة البرنامج Lesson15_01 الأساسيّة هي التي استُخدمت، ستكون قيمة average تساوي 77.5، ويمثّل هذا الرقم معدّل الطلاب الذين تكون درجاتهم أكبر تمامًا من 60. قد يبدو كلّ ما قدّمناه جميلًا وممتعًا، لكنّك لن تستمتع بشكل فعليّ بهذه المزايا الرائعة التي توفّرها Linq و تعابير Lambda ما لم تستخدمها في برامجك الخاصّة. أستخدم مثل هذه المزايا في برامجي التي أطوّرها، ولن تتصوّر مدى سعادتي عندما أستخلص بيانات من قاعدة بيانات أو من خدمة ويب web service قد تحتوي على المئات أو الآلاف من البيانات الخام، ثمّ أُطبّق عليها وصفات Linq السحريّة فأحصل على ما أريده بكتابة عبارة برمجيّة واحدة فقط! تمارين داعمة تمرين 1 أجرِ تعديلًا على البرنامج Lesson15_01 بحيث نحصل على جميع الطلّاب الذين تكون درجاتهم أقل تمامًا من 50. تمرين 2 أجرِ تعديلًا آخرًا على البرنامج Lesson15_01 بحيث نحصل على جميع الطلّاب الذين يكون الحرف الأوّل من اسمهم هو "A". (تلميح: أحد الحلول المقترحة هو استخدام التابع StartWith من الخاصيّة النصية FirstName للكائن student أي على الشكل التالي: student.FirstName.StartsWith("A") وذلك بعد الكلمة where في استعلام Linq ). الخلاصة تعرّفنا في هذا الدرس على تعابير Lambda واستعلامات Linq. حيث صمّمنا عدّة برامج توضّح هاتين التقنيّتين المهمّتين في البرمجة باستخدام سي شارب. ستصادف كلًّا منهما كثيرًا في حياتك البرمجيّة، وستكون سعيدًا باستخدامها نظرًا للاختصار الكبير الذي ستحصل عليه في الشيفرة البرمجيّة، هذا فضلًا عن الأداء عالي المستوى الذي لن تستطيع مجاراته باستخدامك للشيفرة التقليديّة.