C#: الميراث

1. ما هو الميراث؟

يُعد الوراثة آلية أساسية في البرمجة الموجهة للكائنات، حيث تتيح لفئة ما إعادة استخدام عناصر فئة أخرى وتوسيعها بوظائف جديدة. ولا تدعم لغة C# سوى الوراثة الأحادية، مما يعني أنه لا يمكن أن يكون للفئة سوى فئة أساسية مباشرة واحدة.


2. قواعد بناء جملة التوريث

استخدم class DerivedClass : BaseClass لإعلان علاقة وراثة. تحصل الفئة المشتقة تلقائيًا على جميع العناصر غير الخاصة في الفئة الأساسية.

▶ مثال

CSHARP
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();
    }
}
▶ جرّب الكود
TEXT 📖 للعرض فقط
Buddy is eating.
Buddy: Woof!

3. الكلمة الرئيسية الأساسية

يُستخدم base لاستدعاء منشئات الفئة الأساسية وطرقها الأعضاء. في المنشئ، قم بتمرير الحجج إلى المنشئ الأصلي عبر base(args)؛ وفي الطريقة، قم باستدعاء التنفيذ الأصلي عبر base.Method().

▶ مثال

CSHARP
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();
    }
}
▶ جرّب الكود
TEXT 📖 للعرض فقط
I am Alice.
I am in grade 3.

4. تجاوز الطرق باستخدام الكلمة «virtual» أو «override»

تُشير الفئة الأساسية إلى الطرق القابلة للتجاوز بعلامة virtual، بينما توفر الفئة المشتقة تنفيذًا جديدًا باستخدام override. وهذا هو أساس تعدد الأشكال.

▶ مثال

CSHARP 📖 للعرض فقط
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}");
        }
    }
}
44 سطر من الكود المنطقي (تجاوز الحد 40, للعرض فقط)
TEXT 📖 للعرض فقط
Area: 12.57
Area: 12.00

5. إخفاء الدالة باستخدام كلمة «new»

إذا عرَّفت فئة مشتقة طريقة تحمل نفس اسم طريقة موجودة في الفئة الأساسية دون استخدام override، فإن المُترجم يصدر تحذيرًا. تعمل الكلمة الرئيسية new على إخفاء طريقة الفئة الأساسية بشكل صريح، مما يشير إلى أن هذه الطريقة مستقلة وليست تجاوزًا.

▶ مثال

CSHARP
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();
    }
}
▶ جرّب الكود
TEXT 📖 للعرض فقط
Hello from Derived
Hello from Base
💡 الفرق بين إخفاء new وتجاوز override: عند استدعاء new من خلال مرجع للفئة الأساسية، يتم تنفيذ النسخة الخاصة بالفئة الأساسية، بينما يتم تنفيذ النسخة الخاصة بالفئة المشتقة عند استدعاء override.


6. sealed: منع التوريث

إن تطبيق sealed على فئة ما يعني أنه لا يمكن توريثها؛ أما تطبيقه على دالة ما فيعني أنه لا يمكن تجاوزها مرة أخرى.

▶ مثال

CSHARP
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();
    }
}
▶ جرّب الكود
TEXT 📖 للعرض فقط
Loading JSON config...
⚠️ بمجرد وضع علامة sealed على JsonConfig.Load، لن تتمكن أي فئة مشتقة من JsonConfig من تجاوز Load. بمجرد تعيين FinalClass إلى sealed، لن تتمكن أي فئة من التوريث منه على الإطلاق.


7. تسلسل منشئات الدالة في التوريث

عند إنشاء كائن من فئة مشتقة، يتم دائمًا تنفيذ مُنشئ الفئة الأساسية قبل مُنشئ الفئة المشتقة. وإذا لم تكن الفئة الأساسية تحتوي على مُنشئ بدون معلمات، فيجب على الفئة المشتقة استدعاء base(args) بشكل صريح.

▶ مثال

CSHARP
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");
    }
}
▶ جرّب الكود
TEXT 📖 للعرض فقط
Vehicle(Car) constructed
Car(Toyota) constructed

8. الفئة الجذرية للكائن

ترث جميع الأنواع في لغة C# من System.Object (المعروف أيضًا باسم object). ويوفر هذا النوع طرقًا أساسية مثل ToString() وEquals() وGetHashCode() وGetType().

▶ مثال

CSHARP
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);
    }
}
▶ جرّب الكود
TEXT 📖 للعرض فقط
(1, 2)
True
Point

9. عوامل «is» و«as»

تقوم is بفحص النوع وتُرجع bool؛ بينما تقوم as بتحويل آمن للنوع، وتُرجع null في حالة الفشل بدلاً من إلقاء استثناء.

▶ مثال

CSHARP
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}");
    }
}
▶ جرّب الكود
TEXT 📖 للعرض فقط
a is a Cat
a is not a Dog
Meow!
as Dog result is null: True
💡 يدعم C# 7+ مطابقة الأنماط is: حيث يمكن لـ if (a is Cat cat) التحقق من النوع وإعلان متغير في آن واحد.


❓ أسئلة شائعة

س هل تدعم لغة C# الوراثة المتعددة؟
ج لا. لا يمكن أن يكون للفئة سوى فئة أساسية مباشرة واحدة، لكن يمكنها تنفيذ واجهات متعددة.
س ماذا يحدث إذا كتبتُ دالة تحمل الاسم نفسه دون استخدام الكلمة override؟
ج يصدر المُترجم تحذيرًا. حيث تُخفي الدالة دالة الفئة الأساسية ضمناً؛ ويُنصح بإضافة new لإعلان ذلك صراحةً.
س هل يمكن استخدام الكلمة الرئيسية base في الطرق الثابتة؟
ج لا. فكل من base وthis تشيران إلى مثيل، والطرق الثابتة لا تحتوي على سياق مثيل.
س ما هي الأهمية العملية للفئة المُغلقة؟
ج إنها تمنع توريث الفئة، وتضمن عدم إمكانية تعديل تنفيذها، وتسمح لمُترجم JIT بإجراء المزيد من عمليات التحسين.
س ما الفرق بين is و as؟
ج تُرجع is قيمة منطقية (bool) للتحقق من النوع؛ أما as فتحاول إجراء تحويل وتُرجع الكائن أو القيمة null. ولا يمكن استخدام as مع أنواع القيم.

📖 ملخص

📝 تمارين

  1. قم بتعريف فئة أساسية Employee (تحتوي على الخصائص Name وSalary)، ثم قم بإنشاء فئة مشتقة Manager (تحتوي على خاصية إضافية Level)، واستخدم base في منشئ Manager لاستدعاء منشئ الفئة الأصلية.
  2. قم بتعريف دالة virtual void Work() في Employee، ثم قم بتجاوزها باستخدام override في Manager، واستدعِ base.Work() داخل الدالة التي تم تجاوزها.
  3. قم بإنشاء مصفوفة Employee[] تحتوي على كائني Employee وManager، واستخدم عامل is لتحديد النوع، ثم اعرض معلومات مختلفة وفقًا لذلك.
  4. قم بتجاوز طريقة ToString() في Manager بحيث تُرجع سلسلة تحتوي على Name وSalary وLevel.
  5. قم بتعريف فئة sealed باسم PayCalculator، ثم حاول كتابة فئة ترث عنها ولاحظ خطأ الترجمة.
Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%