الـ Delegates والأحداث
جزء من قسم Object Oriented Programming في رحلة C# على Coddy — الدرس 43 من 70.
الـ delegate هو نوع يمثل مرجعاً لدالة (method). فكر فيه كمتغير يحمل دالة بدلاً من البيانات. يتيح لك ذلك تمرير الدوال كـ arguments، وتخزينها في مجموعات، واستدعاؤها بشكل ديناميكي.
تقوم بالتصريح عن مفوض (delegate) من خلال تحديد توقيع الطريقة (method signature) التي يمكنه الرجوع إليها:
public delegate void Notify(string message);
public class Publisher
{
public void SendAlert(string msg) => Console.WriteLine("Alert: " + msg);
}
// الاستخدام
Notify notifier = new Publisher().SendAlert;
notifier("System started"); // Alert: System startedتعتمد الأحداث (Events) على المفوضات (delegates) لتنفيذ نمط النشر والاشتراك (publish-subscribe pattern). تقوم الفئة (class) بإطلاق حدث، ويمكن للفئات الأخرى الاشتراك لتلقي الإشعارات. تقيد الكلمة المفتاحية event كيفية استخدام المفوض - حيث يمكن للمشتركين فقط إضافة أو إزالة المعالجات (handlers)، ولا يمكنهم استدعاء الحدث مباشرة.
public class Button
{
public event Action Clicked;
public void Click()
{
Clicked?.Invoke(); // إخطار جميع المشتركين
}
}
// الاشتراك
Button btn = new Button();
btn.Clicked += () => Console.WriteLine("Button was clicked!");
btn.Click(); // تم النقر على الزر!يقوم عامل += بالاشتراك بمعالج، بينما يقوم -= بإلغاء الاشتراك. يستدعي نمط ?.Invoke() الحدث بأمان فقط في حالة وجود مشتركين. تُعد الأحداث أساسية لتطبيقات واجهة المستخدم الرسومية (GUI)، ومحركات الألعاب، وأي نظام تحتاج فيه الكائنات إلى التفاعل مع التغييرات دون ارتباط وثيق.
التحدي
سهللنقم ببناء نظام مراقبة درجة الحرارة الذي يستخدم المفوضات (delegates) والأحداث (events) لإخطار المشتركين كلما تغيرت درجة الحرارة. هذا مثال كلاسيكي على نمط النشر والاشتراك (publish-subscribe pattern) الذي تتيحه الأحداث بشكل أنيق للغاية.
ستقوم بتنظيم الكود الخاص بك عبر ملفين:
TemperatureMonitor.cs: قم بإنشاء فئةTemperatureMonitorفي مساحة الأسماءMonitoringوالتي تعمل كناشر. يجب أن يقوم المراقب الخاص بك بما يلي:- امتلاك حقل خاص (private field) لتخزين درجة الحرارة الحالية
- الإعلان عن حدث باستخدام
Action<int>يسمىTemperatureChangedينطلق كلما تم تحديث درجة الحرارة - امتلاك خاصية
Temperature(من نوع int) حيث يقوم الـ setter بتحديث الحقل الخاص ويرفع الحدث بقيمة درجة الحرارة الجديدة
?.Invoke()لرفع الحدث بأمان فقط عندما يكون هناك مشتركون.Program.cs: في ملفك الرئيسي، قم بإنشاءTemperatureMonitorواشترك بمعالجين مختلفين في حدثTemperatureChangedالخاص به:- معالج واحد يطبع:
Alert: Temperature is now {value} degrees - معالج آخر يطبع:
Logged: {value}
- معالج واحد يطبع:
ستتلقى مدخلين:
- قيمة درجة الحرارة الأولى (عدد صحيح)
- قيمة درجة الحرارة الثانية (عدد صحيح)
على سبيل المثال، إذا كانت المدخلات هي 25 و 30، يجب أن يكون المخرج:
Alert: Temperature is now 25 degrees
Logged: 25
Alert: Temperature is now 30 degrees
Logged: 30لاحظ كيف أن تعيين خاصية Temperature يقوم تلقائيًا بإخطار جميع المشتركين - لا يحتاج المراقب إلى معرفة أي شيء عن من يستمع. هذا هو جمال النهج القائم على الأحداث!
ورقة مرجعية
الـ delegate هو نوع يمثل مرجعاً إلى طريقة (method)، مما يسمح لك بتمرير الطرق كوسطاء، وتخزينها في مجموعات، واستدعائها ديناميكياً.
قم بالتصريح عن delegate من خلال تحديد توقيع الطريقة (method signature):
public delegate void Notify(string message);
public class Publisher
{
public void SendAlert(string msg) => Console.WriteLine("Alert: " + msg);
}
// الاستخدام
Notify notifier = new Publisher().SendAlert;
notifier("System started"); // Alert: System startedتقوم الـ Events بتنفيذ نمط النشر والاشتراك (publish-subscribe pattern) باستخدام الـ delegates. تقيد الكلمة المفتاحية event استخدام الـ delegate - حيث يمكن للمشتركين فقط إضافة أو إزالة المعالجات (handlers)، وليس استدعاء الحدث مباشرة.
public class Button
{
public event Action Clicked;
public void Click()
{
Clicked?.Invoke(); // إخطار جميع المشتركين
}
}
// الاشتراك
Button btn = new Button();
btn.Clicked += () => Console.WriteLine("Button was clicked!");
btn.Click(); // Button was clicked!استخدم += للاشتراك في معالج و -= لإلغاء الاشتراك. يقوم نمط ?.Invoke() باستدعاء الحدث بأمان فقط في حالة وجود مشتركين.
جرّب بنفسك
using System;
using Monitoring;
class Program
{
public static void Main(string[] args)
{
// قراءة المدخلات
int temp1 = Convert.ToInt32(Console.ReadLine());
int temp2 = Convert.ToInt32(Console.ReadLine());
// TODO: إنشاء مثيل (instance) من TemperatureMonitor
// TODO: الاشتراك بمعالج (handler) يطبع: "Alert: Temperature is now {value} degrees"
// TODO: الاشتراك بمعالج آخر يطبع: "Logged: {value}"
// TODO: تعيين درجة الحرارة إلى temp1
// TODO: تعيين درجة الحرارة إلى temp2
}
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
1أساسيات OOP
الملفات الخارجيةNamespaces والتوجيهاتمقدمة في Classes و Objectsالكلمة المفتاحية 'this'Methods والمعاملاتFields مقابل PropertiesConstructorsObject Initializersمراجعة - آلة حاسبة بسيطة4الوراثة
صيغة الوراثة الأساسية (:)الكلمة المفتاحية 'base'الكلمات المفتاحية Virtual و Overrideالأصناف المختومة (Sealed Classes)الفئة الأساسية 'object'مراجعة - هيكلية الموظفين7ميزات متقدمة
تحميل العوامل (Operator Overloading)المفهرسات (Indexers (this[]))إعادة تعريف ()ToStringدوال التوسعة (Extension Methods)ملخص - Custom List2الخصائص والأعضاء الساكنة (Static Members)
الخصائص المطبقة تلقائيًا (Auto-Implemented Properties)خصائص القراءة فقط والكتابة فقطالحقول والدوال الساكنة (Static)الفئات الساكنة (Static Classes)الأعضاء ذات البنية التعبيرية (Expression-Bodied Members)5تعدد الأشكال والواجهات (Polymorphism & Interfaces)
تعدد الأشكال: وقت التجميع مقابل وقت التشغيلالواجهة مقابل الفئة المجردة (Abstract Class)الواجهات المتعددةالواجهات الصريحةUpcasting و Downcastingملخص - حاسبة الأشكال8مفاهيم البرمجة كائنية التوجه (OOP) المتقدمة
التركيب بدلاً من الوراثةالـ Generics (الأصناف والدوال)الـ Delegates والأحداثالـ Attributes والـ Reflectionواجهة IDisposable وجملة usingأساسيات Dependency Injection