الكلمات المفتاحية Virtual و Override
جزء من قسم Object Oriented Programming في رحلة C# على Coddy — الدرس 21 من 70.
عندما ترث فئة مشتقة دالة ما، قد ترغب أحياناً في تغيير طريقة سلوك هذه الدالة تماماً. تعمل الكلمتان المفتاحيتان virtual و override معاً لجعل هذا ممكناً بطريقة محكومة.
قم بتمييز الطريقة كـ virtual في الفئة الأساسية للإشارة إلى أنه يُسمح للفئات المشتقة باستبدال تنفيذها:
public class Animal
{
public virtual void Speak()
{
Console.WriteLine("Some sound");
}
}
public class Dog : Animal
{
public override void Speak()
{
Console.WriteLine("Woof!");
}
}تخبر الكلمة المفتاحية override في الفئة المشتقة (derived class) المترجم بأنك تقوم باستبدال الطريقة الأساسية (base method) عن قصد. وبدون وجود virtual في الطريقة الأساسية، لا يمكنك استخدام override.
تظهر القوة الحقيقية عندما تستخدم مرجعاً من الفئة الأساسية (base class) للإشارة إلى كائن مشتق (derived object):
Animal myPet = new Dog();
myPet.Speak(); // الناتج: Woof!على الرغم من أن myPet تم التصريح عنه كـ Animal، إلا أن C# يستدعي نسخة Dog من Speak(). يسمى هذا تعدد الأشكال في وقت التشغيل (runtime polymorphism) - تعتمد الطريقة الفعلية التي يتم استدعاؤها على النوع الحقيقي للكائن، وليس على النوع المصرح به للمتغير.
لا يزال بإمكانك الوصول إلى التنفيذ الأساسي باستخدام base.Speak() داخل التجاوز (override) الخاص بك إذا كنت ترغب في توسيع السلوك بدلاً من استبداله بالكامل.
التحدي
سهللنقم ببناء نظام إشعارات يوضح كيف يمكن للفئات المشتقة تجاوز سلوك الفئة الأساسية تماماً باستخدام الكلمات المفتاحية virtual و override، مع إظهار قوة تعدد الأشكال في وقت التشغيل (runtime polymorphism).
ستقوم بإنشاء ثلاثة ملفات لتنظيم الكود الخاص بك:
Notification.cs: قم بتعريف فئةNotificationداخل مساحة الأسماءAlerts. تمثل هذه الفئة الأساسية إشعاراً عاماً مع خاصيةMessage(سلسلة نصية) ومنشئ (constructor) يقوم بتعيينها. قم بتضمين تابعvirtualيسمىSend()يعيد القيمة"Sending notification: {Message}".EmailNotification.cs: قم بتعريف فئةEmailNotificationداخل مساحة الأسماءAlertsالتي ترث منNotification. أضف خاصيةRecipient(سلسلة نصية) ومنشئاً يقبل كل من الرسالة والمستلم (استخدمbaseلتمرير الرسالة). قم بعملoverrideللتابعSend()ليعيد"Emailing {Recipient}: {Message}".SmsNotification.cs: قم بتعريف فئةSmsNotificationداخل مساحة الأسماءAlertsالتي ترث أيضاً منNotification. أضف خاصيةPhoneNumber(سلسلة نصية) ومنشئاً يقبل كل من الرسالة ورقم الهاتف. قم بعملoverrideللتابعSend()ليعيد"SMS to {PhoneNumber}: {Message}".Program.cs: في ملفك الرئيسي، قم بإنشاء مثيلات (instances) من جميع أنواع الإشعارات الثلاثة باستخدام قيم الإدخال. ثم وضح تعدد الأشكال في وقت التشغيل عن طريق تخزين الثلاثة في متغيرات من نوعNotificationواستدعاءSend()لكل منها.
ستتلقى أربعة مدخلات:
- رسالة الإشعار
- عنوان البريد الإلكتروني للمستلم
- رقم الهاتف لرسالة SMS
اطبع نتيجة استدعاء Send() لكل إشعار، مخزنة كمرجع للفئة الأساسية:
{base notification Send() result}
{email notification Send() result}
{sms notification Send() result}على سبيل المثال، إذا كانت المدخلات هي Meeting at 3pm، و alice@email.com، و 555-1234، يجب أن يكون المخرج:
Sending notification: Meeting at 3pm
Emailing alice@email.com: Meeting at 3pm
SMS to 555-1234: Meeting at 3pmلاحظ كيف أنه على الرغم من تخزين الثلاثة كمتغيرات من نوع Notification، إلا أن كل واحد منها يستدعي نسخته الخاصة التي تم تجاوزها من Send(). هذا هو تعدد الأشكال في وقت التشغيل أثناء العمل!
ورقة مرجعية
استخدم الكلمة المفتاحية virtual في الفئة الأساسية (base class) لتمييز الطريقة (method) التي يمكن للفئات المشتقة (derived classes) تجاوزها:
public class Animal
{
public virtual void Speak()
{
Console.WriteLine("Some sound");
}
}استخدم الكلمة المفتاحية override في الفئة المشتقة لاستبدال تنفيذ الطريقة في الفئة الأساسية:
public class Dog : Animal
{
public override void Speak()
{
Console.WriteLine("Woof!");
}
}يحدث تعدد الأشكال في وقت التشغيل (Runtime polymorphism) عندما يحمل مرجع الفئة الأساسية كائناً مشتقاً - تعتمد الطريقة الفعلية التي يتم استدعاؤها على النوع الحقيقي للكائن، وليس على النوع المعلن للمتغير:
Animal myPet = new Dog();
myPet.Speak(); // Output: Woof!يمكنك الوصول إلى تنفيذ الفئة الأساسية داخل التجاوز (override) باستخدام base:
public override void Speak()
{
base.Speak(); // يستدعي نسخة الفئة الأساسية
Console.WriteLine("Woof!");
}جرّب بنفسك
using System;
using Alerts;
class Program
{
public static void Main(string[] args)
{
// قراءة المدخلات
string message = Console.ReadLine();
string email = Console.ReadLine();
string phone = Console.ReadLine();
// TODO: إنشاء مثيلات (instances) لجميع أنواع الإشعارات الثلاثة
// تخزينها في متغيرات من نوع Notification لتوضيح تعدد الأشكال (polymorphism)
// TODO: إنشاء Notification أساسي
Notification baseNotification;
// TODO: إنشاء EmailNotification (مخزن كنوع Notification)
Notification emailNotification;
// TODO: إنشاء SmsNotification (مخزن كنوع Notification)
Notification smsNotification;
// TODO: استدعاء Send() لكل منها وطباعة النتائج
// سيتم استدعاء الدوال التي تم تجاوزها (overridden methods) بسبب تعدد الأشكال في وقت التشغيل (runtime polymorphism)
}
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس 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