C#: الميراث
1. ما هو الميراث؟
يُعد الوراثة آلية أساسية في البرمجة الموجهة للكائنات، حيث تتيح لفئة ما إعادة استخدام عناصر فئة أخرى وتوسيعها بوظائف جديدة. ولا تدعم لغة C# سوى الوراثة الأحادية، مما يعني أنه لا يمكن أن يكون للفئة سوى فئة أساسية مباشرة واحدة.
2. قواعد بناء جملة التوريث
استخدم class DerivedClass : BaseClass لإعلان علاقة وراثة. تحصل الفئة المشتقة تلقائيًا على جميع العناصر غير الخاصة في الفئة الأساسية.
▶ مثال
class Animal
{
public string Name { get; set; }
public void Eat()
{
Console.WriteLine($"{Name} is eating.");
}
}
class Dog : Animal
{
public void Bark()
{
Console.WriteLine($"{Name}: Woof!");
}
}
class Program
{
static void Main()
{
Dog dog = new Dog { Name = "Buddy" };
dog.Eat();
dog.Bark();
}
}
Buddy is eating.
Buddy: Woof!
3. الكلمة الرئيسية الأساسية
يُستخدم base لاستدعاء منشئات الفئة الأساسية وطرقها الأعضاء. في المنشئ، قم بتمرير الحجج إلى المنشئ الأصلي عبر base(args)؛ وفي الطريقة، قم باستدعاء التنفيذ الأصلي عبر base.Method().
▶ مثال
class Person
{
public string Name { get; }
public Person(string name)
{
Name = name;
}
public virtual void Introduce()
{
Console.WriteLine($"I am {Name}.");
}
}
class Student : Person
{
public int Grade { get; }
public Student(string name, int grade) : base(name)
{
Grade = grade;
}
public override void Introduce()
{
base.Introduce();
Console.WriteLine($"I am in grade {Grade}.");
}
}
class Program
{
static void Main()
{
Student s = new Student("Alice", 3);
s.Introduce();
}
}
I am Alice.
I am in grade 3.
4. تجاوز الطرق باستخدام الكلمة «virtual» أو «override»
تُشير الفئة الأساسية إلى الطرق القابلة للتجاوز بعلامة virtual، بينما توفر الفئة المشتقة تنفيذًا جديدًا باستخدام override. وهذا هو أساس تعدد الأشكال.
▶ مثال
class Shape
{
public virtual double Area()
{
return 0;
}
}
class Circle : Shape
{
public double Radius { get; set; }
public Circle(double radius)
{
Radius = radius;
}
public override double Area()
{
return Math.PI * Radius * Radius;
}
}
class Rectangle : Shape
{
public double Width { get; set; }
public double Height { get; set; }
public Rectangle(double w, double h)
{
Width = w;
Height = h;
}
public override double Area()
{
return Width * Height;
}
}
class Program
{
static void Main()
{
Shape[] shapes = { new Circle(2), new Rectangle(3, 4) };
foreach (Shape s in shapes)
{
Console.WriteLine($"Area: {s.Area():F2}");
}
}
}
Area: 12.57
Area: 12.00
5. إخفاء الدالة باستخدام كلمة «new»
إذا عرَّفت فئة مشتقة طريقة تحمل نفس اسم طريقة موجودة في الفئة الأساسية دون استخدام override، فإن المُترجم يصدر تحذيرًا. تعمل الكلمة الرئيسية new على إخفاء طريقة الفئة الأساسية بشكل صريح، مما يشير إلى أن هذه الطريقة مستقلة وليست تجاوزًا.
▶ مثال
class Base
{
public void Greet()
{
Console.WriteLine("Hello from Base");
}
}
class Derived : Base
{
public new void Greet()
{
Console.WriteLine("Hello from Derived");
}
}
class Program
{
static void Main()
{
Derived d = new Derived();
d.Greet();
Base b = d;
b.Greet();
}
}
Hello from Derived
Hello from Base
new وتجاوز override: عند استدعاء new من خلال مرجع للفئة الأساسية، يتم تنفيذ النسخة الخاصة بالفئة الأساسية، بينما يتم تنفيذ النسخة الخاصة بالفئة المشتقة عند استدعاء override.
6. sealed: منع التوريث
إن تطبيق sealed على فئة ما يعني أنه لا يمكن توريثها؛ أما تطبيقه على دالة ما فيعني أنه لا يمكن تجاوزها مرة أخرى.
▶ مثال
class Config
{
public virtual void Load()
{
Console.WriteLine("Loading config...");
}
}
class JsonConfig : Config
{
public sealed override void Load()
{
Console.WriteLine("Loading JSON config...");
}
}
sealed class FinalClass
{
public void DoWork() { }
}
class Program
{
static void Main()
{
JsonConfig cfg = new JsonConfig();
cfg.Load();
}
}
Loading JSON config...
sealed على JsonConfig.Load، لن تتمكن أي فئة مشتقة من JsonConfig من تجاوز Load. بمجرد تعيين FinalClass إلى sealed، لن تتمكن أي فئة من التوريث منه على الإطلاق.
7. تسلسل منشئات الدالة في التوريث
عند إنشاء كائن من فئة مشتقة، يتم دائمًا تنفيذ مُنشئ الفئة الأساسية قبل مُنشئ الفئة المشتقة. وإذا لم تكن الفئة الأساسية تحتوي على مُنشئ بدون معلمات، فيجب على الفئة المشتقة استدعاء base(args) بشكل صريح.
▶ مثال
class Vehicle
{
public string Type { get; }
public Vehicle(string type)
{
Type = type;
Console.WriteLine($"Vehicle({type}) constructed");
}
}
class Car : Vehicle
{
public string Brand { get; }
public Car(string brand) : base("Car")
{
Brand = brand;
Console.WriteLine($"Car({brand}) constructed");
}
}
class Program
{
static void Main()
{
Car car = new Car("Toyota");
}
}
Vehicle(Car) constructed
Car(Toyota) constructed
8. الفئة الجذرية للكائن
ترث جميع الأنواع في لغة C# من System.Object (المعروف أيضًا باسم object). ويوفر هذا النوع طرقًا أساسية مثل ToString() وEquals() وGetHashCode() وGetType().
▶ مثال
class Point
{
public int X { get; }
public int Y { get; }
public Point(int x, int y)
{
X = x;
Y = y;
}
public override string ToString()
{
return $"({X}, {Y})";
}
public override bool Equals(object obj)
{
if (obj is Point other)
{
return X == other.X && Y == other.Y;
}
return false;
}
public override int GetHashCode()
{
return HashCode.Combine(X, Y);
}
}
class Program
{
static void Main()
{
Point p1 = new Point(1, 2);
Point p2 = new Point(1, 2);
Console.WriteLine(p1.ToString());
Console.WriteLine(p1.Equals(p2));
Console.WriteLine(p1.GetType().Name);
}
}
(1, 2)
True
Point
9. عوامل «is» و«as»
تقوم is بفحص النوع وتُرجع bool؛ بينما تقوم as بتحويل آمن للنوع، وتُرجع null في حالة الفشل بدلاً من إلقاء استثناء.
▶ مثال
class Animal { }
class Cat : Animal
{
public void Meow() { Console.WriteLine("Meow!"); }
}
class Dog : Animal
{
public void Bark() { Console.WriteLine("Woof!"); }
}
class Program
{
static void Main()
{
Animal a = new Cat();
if (a is Cat)
{
Console.WriteLine("a is a Cat");
}
if (a is Dog)
{
Console.WriteLine("a is a Dog");
}
else
{
Console.WriteLine("a is not a Dog");
}
Cat c = a as Cat;
if (c != null)
{
c.Meow();
}
Dog d = a as Dog;
Console.WriteLine($"as Dog result is null: {d == null}");
}
}
a is a Cat
a is not a Dog
Meow!
as Dog result is null: True
is: حيث يمكن لـ if (a is Cat cat) التحقق من النوع وإعلان متغير في آن واحد.
❓ أسئلة شائعة
new لإعلان ذلك صراحةً.base في الطرق الثابتة؟base وthis تشيران إلى مثيل، والطرق الثابتة لا تحتوي على سياق مثيل.📖 ملخص
- يستخدم التوريث صيغة
class Derived : Base؛ بينما لا يدعم لغة C# سوى التوريث الأحادي - تستدعي الكلمة الرئيسية
baseمنشئ الفئة الأم والطرق الخاصة بأعضائها virtual/overrideيُنفِّذ تجاوز الطرق، وهو أساس تعدد الأشكال- تعمل الكلمة الرئيسية
newعلى إخفاء إحدى طرق الفئة الأساسية بشكل صريح، وتختلف في سلوكها عن عملية التجاوز sealedعند استخدامها على فئة، تمنع التوريث؛ وعند استخدامها على دالة، تمنع التجاوز مرة أخرى- يتم تنفيذ منشئات الكائنات بالترتيب من الفئة الأساسية إلى الفئة المشتقة
- جميع الأنواع ترث من
object، ويمكنك تجاوزToStringوEqualsوGetHashCode isيقوم بالتحقق من النوع؛asيقوم بالتحويل الآمن
📝 تمارين
- قم بتعريف فئة أساسية
Employee(تحتوي على الخصائصNameوSalary)، ثم قم بإنشاء فئة مشتقةManager(تحتوي على خاصية إضافيةLevel)، واستخدمbaseفي منشئManagerلاستدعاء منشئ الفئة الأصلية. - قم بتعريف دالة
virtual void Work()فيEmployee، ثم قم بتجاوزها باستخدامoverrideفيManager، واستدعِbase.Work()داخل الدالة التي تم تجاوزها. - قم بإنشاء مصفوفة
Employee[]تحتوي على كائنيEmployeeوManager، واستخدم عاملisلتحديد النوع، ثم اعرض معلومات مختلفة وفقًا لذلك. - قم بتجاوز طريقة
ToString()فيManagerبحيث تُرجع سلسلة تحتوي علىNameوSalaryوLevel. - قم بتعريف فئة
sealedباسمPayCalculator، ثم حاول كتابة فئة ترث عنها ولاحظ خطأ الترجمة.