מה זה?

תארו לעצמכם מערכת עם Employee ו-Manager — למנהל יש את כל מה שיש לעובד רגיל (שם, משכורת) ועוד קצת (צוות שהוא מנהל). בלי ירושה, Manager היה מעתיק-מדביק את כל השדות והמתודות של Employee מחדש — כפילות שמסתכנת בחוסר-סנכרון אם Employee משתנה. ירושה (: BaseClass) פותרת את זה: Manager “יורש” אוטומטית את כל מה שיש ל-Employee, ומוסיף רק את מה שייחודי לו.

מילות מפתח שחשוב לזכור

• Base Class (מחלקת בסיס) — המחלקה שממנה יורשים; מגדירה את ההתנהגות המשותפת

• Derived Class (מחלקה נגזרת) — המחלקה שיורשת; מקבלת אוטומטית את כל מה שב-Base Class

• virtual — מסמן מתודה ב-Base Class כ"ניתנת לשכתוב" על ידי מחלקות יורשות

• override — מחלקה יורשת מגדירה מחדש מתודה virtual, עם התנהגות ייחודית לה

• base — מתייחס למימוש של המחלקה שממנה יורשים; שימושי לקרוא למימוש המקורי מתוך override

class Employee
{
    public string Name;
    public double Salary;

    public Employee(string name, double salary)
    {
        Name = name;
        Salary = salary;
    }

    public virtual string Describe()
    {
        return $"{Name}, employee, salary {Salary}";
    }
}

class Manager : Employee   // Manager inherits from Employee
{
    public List<string> TeamMembers = new List<string>();

    public Manager(string name, double salary) : base(name, salary) { }

    public override string Describe()
    {
        return $"{base.Describe()}, manages a team of {TeamMembers.Count} people";
    }
}
Employee employee = new Employee("Dana", 15000);
Manager manager = new Manager("Avi", 22000);
manager.TeamMembers.Add("Rotem");

Console.WriteLine(employee.Describe());   // Dana, employee, salary 15000
Console.WriteLine(manager.Describe());    // Avi, employee, salary 22000, manages a team of 1 people
  classDiagram
    class Employee {
      +string Name
      +double Salary
      +Describe() virtual
    }
    class Manager {
      +List~string~ TeamMembers
      +Describe() override
    }
    Employee <|-- Manager

הסבר עיקרי

: base(name, salary) — קונסטרוקטור של מחלקה יורשת חייב לוודא שהקונסטרוקטור של ה-Base Class רץ קודם, כדי ש-Name/Salary יאותחלו כראוי. התחביר : base(...) מעביר את הפרמטרים לקונסטרוקטור של Employee באופן מפורש.

למה virtual ו-override הכרחיים ביחד — בלי virtual על Describe ב-Employee, C# לא הייתה מרשה ל-Manager “לשכתב” את ההתנהגות — כל קריאה הייתה מריצה תמיד את הגרסה המקורית. virtual הוא “רישיון לשכתוב”; override הוא המימוש המחודש בפועל במחלקה היורשת.

base.Describe() — בתוך ה-override, אפשר לקרוא למימוש המקורי של ה-Base Class עם base.Describe(), ולהוסיף עליו — במקום לשכתב הכל מאפס. בדוגמה, Manager.Describe משתמש בתיאור הבסיסי של Employee ומוסיף עליו את פרטי הצוות — לא כותב את שם המשכורת מחדש.

יתרונות

ירושה מונעת שכפול קוד בין מחלקות קשורות; virtual/override מאפשרים לכל מחלקה יורשת להתאים התנהגות ספציפית לה, תוך שמירה על ממשק אחיד; base מאפשר להרחיב התנהגות קיימת בלי לשכתב אותה מאפס.

חסרונות

ירושה עמוקה מדי (מחלקה יורשת ממחלקה שיורשת ממחלקה…) הופכת קוד לקשה למעקב; שינוי במחלקת בסיס עלול להשפיע (לפעמים בהפתעה) על כל היורשות ממנה — “שביר” יותר מהרכבה (Composition), נושא שיוזכר בהמשך הקורס.

נקודות חשובות

• Derived Class מקבל אוטומטית את כל השדות/המתודות public/protected של ה-Base Class

• virtual ב-Base Class מאפשר override במחלקה יורשת; בלי virtual אי אפשר לשכתב

• base(...) בקונסטרוקטור מעביר פרמטרים לקונסטרוקטור של מחלקת הבסיס

• base.Method() בתוך override קורא למימוש המקורי, לא רק “מדלג” עליו

טעויות נפוצות

• לשכוח virtual על מתודה ב-Base Class ואז לתהות למה override לא מתקמפל

• לא לקרוא לקונסטרוקטור הבסיס (: base(...)) ולהשאיר שדות יורשים לא מאותחלים

• להשתמש בירושה כשבפועל אין קשר “הוא-סוג-של” אמיתי בין המחלקות (למשל Car : Engine — רכב הוא לא סוג של מנוע, הוא מכיל מנוע)

סיכום

ירושה (class Manager : Employee) מאפשרת למחלקה נגזרת לקבל אוטומטית שדות והתנהגות ממחלקת בסיס, ולהוסיף או לשנות רק את מה שייחודי לה. virtual על ה-Base Class מאפשר override במחלקה היורשת, ו-base מאפשר גישה למימוש המקורי — גם לקונסטרוקטור וגם למתודות — כדי להרחיב ולא לשכפל.

דוקומנטציה רשמית

Microsoft Learn — Inheritance


תרגילים

תרגיל 1 — ירושה בסיסית

המשימה: הגדירו class Animal עם Name ומתודת virtual בשם MakeSound() שמדפיסה “השמעת קול כללית”. הגדירו class Dog : Animal עם override ל-MakeSound() שמדפיסה “הב הב!”.

בדיקה: קריאה ל-MakeSound() על מופע Animal מדפיסה את הקול הכללי; על מופע Dog מדפיסה “הב הב!”.

תרגיל 2 — base בקונסטרוקטור ובמתודה

המשימה: בדוגמת Employee/Manager מהשיעור, הוסיפו למחלקת Manager שדה Department, קונסטרוקטור שמעביר אותו דרך : base(...), ומתודת Describe שמשתמשת ב-base.Describe() ומוסיפה את המחלקה.

בדיקה: הדפסת manager.Describe() כוללת גם את פרטי Employee המקוריים וגם את המחלקה, בלי כפילות קוד.


פרויקט מסכם

המשימה: בנו היררכיית Shape פשוטה: class Shape עם virtual double Area() שמחזירה 0, ושתי מחלקות יורשות: Circle (עם Radius) ו-Rectangle (עם Width/Height), כל אחת עם override נכון לחישוב השטח שלה.

דרישות:

  1. Shape עם קונסטרוקטור בסיסי ומתודת virtual Area()
  2. Circle : Shape ו-Rectangle : Shape, כל אחת עם override Area() נכון (π * r² ו-width * height)
  3. List<Shape> שמכילה כמה עיגולים ומלבנים, ולולאה שמדפיסה את השטח של כל אחד עם foreach

בדיקה: הלולאה מדפיסה שטח נכון לכל צורה, למרות שכולן מטופלות דרך אותו טיפוס Shape ברשימה — זו בדיוק ההוכחה שכל מופע “יודע” להריץ את ה-override הנכון שלו.

מה בפרק הבא

בפרק הבא — Abstract Classes, sealed ופולימורפיזם: מה קורה כשמחלקת בסיס לא אמורה בכלל להיות ניתנת ליצירה ישירה, ואיך C# אוכפת את זה.