Menu

מחלקה אבסטרקטית ב-C#: מתודות אבסטרקטיות, דוגמאות והשוואה ל-interface

מהי מחלקה אבסטרקטית ב-C#: מחלקת בסיס שאי אפשר ליצור ממנה מופע, ושיכולה להצהיר על איברים אבסטרקטיים שהמחלקות היורשות חייבות לממש. כולל מתודות ומאפיינים אבסטרקטיים, בנאים, תבנית template method ומתי עדיף לבחור ב-interface.

בדף הזה יש עורכים שאפשר להריץ - לערוך, להריץ ולראות את הפלט מיד.

מחלקה אבסטרקטית היא מחלקה שקיימת רק כדי שירשו ממנה. אי אפשר ליצור ממנה מופע, והיא יכולה להצהיר על איברים אבסטרקטיים: מתודות ומאפיינים בלי גוף שכל מחלקה יורשת קונקרטית חייבת לממש. בכל השאר זו מחלקה רגילה: יכולים להיות לה שדות, בנאים ומתודות ממומשות במלואן שהמחלקות היורשות מקבלות בירושה.

הצהרה על מחלקה אבסטרקטית

סמנו את המחלקה abstract, וסמנו abstract גם כל איבר שהמחלקות היורשות חייבות לספק, וסיימו אותו בנקודה ופסיק במקום בגוף:

פלט:

Circle: area 7.07, perimeter 9.42
Rectangle: area 10.00, perimeter 13.00

Describe נכתבת פעם אחת, במחלקת הבסיס, וקוראת ל-Area ול-Perimeter, שבכלל לא קיימות ב-Shape. זו בדיוק המטרה של איבר אבסטרקטי: מחלקת הבסיס יכולה לסמוך עליו, והקומפיילר מבטיח שכל צורה קונקרטית מספקת אותו. מחלקה יורשת ששוכחת אחד מהם לא עוברת קומפילציה:

class Triangle : Shape
{
    public Triangle() : base("Triangle") { }
    public override double Area() => 6;
}
// error CS0534: 'Triangle' does not implement inherited abstract member 'Shape.Perimeter()'

הפתרון הוא לממש את Perimeter, או לסמן גם את Triangle עצמה abstract אם היא בסיס ביניים למחלקות נוספות.

כללים לאיברים אבסטרקטיים

  • לאיבר אבסטרקטי אין גוף והוא virtual באופן מובלע: מחלקות יורשות מממשות אותו עם override.
  • רק מחלקות אבסטרקטיות יכולות להכיל איברים אבסטרקטיים. איבר כזה במחלקה רגילה הוא שגיאת קומפילציה.
  • איברים אבסטרקטיים לא יכולים להיות private (אף אחד לא היה יכול לדרוס אותם) או static במחלקה.
  • מתודות, מאפיינים, אינדקסרים ואירועים יכולים להיות אבסטרקטיים. שדות ובנאים לא.
  • מחלקה אבסטרקטית לא חייבת להכיל אף איבר אבסטרקטי. סימון מחלקה כ-abstract לבדו רק מונע יצירת מופעים, וזה שימושי למחלקת בסיס שיש לה משמעות רק דרך תת-המחלקות שלה.

מאפיין אבסטרקטי מצהיר אילו accessors המחלקה היורשת חייבת לספק:

abstract class Plan
{
    public abstract decimal MonthlyPrice { get; }
    public abstract string Name { get; }
}

class ProPlan : Plan
{
    public override decimal MonthlyPrice => 12.99m;
    public override string Name => "Pro";
}

בנאים במחלקות אבסטרקטיות

למחלקה אבסטרקטית יכולים להיות בנאים. אי אפשר לקרוא להם עם new, אבל כל בנאי של מחלקה יורשת מריץ אחד מהם דרך : base(...), ולכן שם מאתחלים ומאמתים את המצב המשותף. הצהירו עליהם כ-protected, וזה אומר בדיוק את זה: רק מחלקות יורשות יכולות להשתמש בהם.

תבנית template method

הסיבה הנפוצה ביותר לכתוב מחלקה אבסטרקטית היא לקבע את הסדר של אלגוריתם במחלקת הבסיס ולתת למחלקות היורשות למלא שלבים בודדים. מתודת הבסיס שמריצה את השלבים אינה virtual, כך שאף תת-מחלקה לא יכולה לשנות את הסדר או לדלג על שלבים.

פלט:

item,qty,price
Coffee beans,2,11.50
Filter papers,1,3.20

| Item | Qty | Price |
| --- | --- | --- |
| Coffee beans | 2 | 11.50 |
| Filter papers | 1 | 3.20 |

Total: 26.20

כל שורה היא tuple עם שמות, והפלט נבנה עם StringBuilder. שימו לב לשילוב: Header ו-Row הן abstract כי כל פורמט חייב להחליט עליהן, ו-Footer היא virtual עם ברירת מחדל ריקה כי רוב הפורמטים לא צריכים שם כלום. השלבים הם protected, כך שקוד חיצוני יכול לקרוא רק ל-Export. גם מחלקות ה-framework משתמשות באותו רעיון: Stream ו-TextWriter הן מחלקות בסיס אבסטרקטיות שהרבה מתודות הנוחות שלהן בנויות על מעט איברים שהמחלקה היורשת מספקת (Read ו-Write ב-stream, Write(char) ב-text writer).

מחלקה אבסטרקטית מול interface

שתיהן מאפשרות לכתוב קוד מול טיפוס בלי להכיר את המחלקה הקונקרטית. הן נבדלות במה שהן יכולות להכיל ובכמה מהן מחלקה יכולה לקבל:

מחלקה אבסטרקטיתInterface
מחלקה יכולה לרשת או לממשאחתרבים
שדות מופע (מצב)כןלא
בנאיםכןלא
מתודות עם גוףכןכן, מאז C# 8 (default interface methods)
איברים סטטייםכןכן, מאז C# 8; איברים static abstract מאז C# 11
מגדירי גישה על איבריםכל אחדpublic כברירת מחדל; אחרים מותרים מאז C# 8
structs יכולים להשתמש בזהלאכן
הוספת איבר בהמשךבטוחה אם הוא לא אבסטרקטישוברת את המממשים, אלא אם יש לו גוף ברירת מחדל

Default interface methods (C# 8) צמצמו את הפער, אבל לא את ההבדל המהותי:

public interface ILogger
{
    void Write(string message);
    void Error(string message) => Write("ERROR: " + message);   // default implementation: C# 8, .NET Core 3.0+
}

מתודת ברירת מחדל עדיין לא יכולה לגשת לשדות מופע, כי ל-interface אין כאלה; היא יכולה רק לקרוא לאיברים אחרים של ה-interface.

דרך מעשית לבחור:

  • Interface כשמתארים יכולת שיכולה להיות לטיפוסים שאינם קשורים זה לזה (IComparable<T>, IDisposable, IShippingProvider), כש-structs צריכים להיות מסוגלים להשתתף, או כשמחלקה צריכה כמה תפקידים כאלה. רוב הקוד של dependency injection נכתב מול interfaces מהסיבה הזו.
  • מחלקה אבסטרקטית כשהטיפוסים היורשים הם באמת וריאציות של דבר אחד וחולקים מצב או אלגוריתם קבוע, כמו ה-exporters שלמעלה.
  • שתיהן זה שילוב נפוץ: interface שהקוד הקורא תלוי בו, ומחלקת בסיס אבסטרקטית שמממשת אותו וחוסכת מהמממשים לכתוב מחדש את החלקים המשותפים. הדף על interfaces מכסה את הצד של ה-interface בפירוט.

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

  • ניסיון ליצור מופע של מחלקת הבסיס. new Shape(...) היא CS0144. צרו מופע של מחלקה יורשת.
  • שכחת override במימוש. כתיבת public double Area() במחלקה היורשת לא מממשת את האיבר האבסטרקטי; הקומפיילר מדווח CS0534 על המימוש החסר, ועוד אזהרה שהמתודה החדשה מסתירה את זו שהתקבלה בירושה.
  • בנאי public במחלקה אבסטרקטית. לא מזיק, אבל מטעה. השתמשו ב-protected.
  • מחלקות אבסטרקטיות בלי קוד משותף. אם למחלקת הבסיס יש רק איברים אבסטרקטיים ואין לה מצב, interface אומר את אותו הדבר ולא תופס את המקום היחיד למחלקת בסיס.
  • קריאה לאיברים אבסטרקטיים מבנאי הבסיס. המימוש של המחלקה היורשת רץ לפני גוף הבנאי של המחלקה היורשת, כך שהשדות שהגוף הזה מציב עדיין 0 או null.

שאלות נפוצות

מהי מחלקה אבסטרקטית ב-C#?

מחלקה שמסומנת abstract נועדה לשמש רק כמחלקת בסיס: אי אפשר ליצור ממנה מופע עם new (שגיאה CS0144). היא יכולה להכיל שדות, בנאים ומתודות רגילים, וגם איברי abstract שאין להם גוף, וכל מחלקה יורשת שאינה אבסטרקטית חייבת לממש אותם.

מה ההבדל בין מחלקה אבסטרקטית ל-interface ב-C#?

מחלקה יכולה לרשת ממחלקה אבסטרקטית אחת בלבד, אבל לממש interfaces רבים. מחלקה אבסטרקטית יכולה להחזיק מצב (שדות מופע), בנאים ואיברים בכל רמת גישה, והיא המקום הטבעי למימוש משותף. interface מתאר יכולת בלי מצב מופע; מאז C# 8 הוא יכול לכלול גוף ברירת מחדל למתודות, אבל עדיין אין לו שדות מופע או בנאים.

מה ההבדל בין מתודות abstract ל-virtual ב-C#?

למתודה virtual יש גוף שמחלקות יורשות רשאיות לדרוס. למתודה abstract אין גוף, ומחלקות יורשות חייבות לדרוס אותה, אחרת הן עצמן אבסטרקטיות (שגיאה CS0534 במחלקה קונקרטית). מתודות אבסטרקטיות יכולות להופיע רק במחלקות אבסטרקטיות.

האם למחלקה אבסטרקטית ב-C# יכול להיות בנאי?

כן. הוא רץ כשנבנה מופע של מחלקה יורשת, דרך : base(...), ובדרך כלל מאתחל את השדות המשותפים. הצהירו עליו כ-protected: בנאי public במחלקה אבסטרקטית רומז שהקוד הקורא יכול להשתמש בו, והוא לא יכול.

למה אי אפשר ליצור מופע של מחלקה אבסטרקטית?

כי ייתכן שיש לה איברים אבסטרקטיים בלי מימוש: לקריאה new Shape().Area() לא היה קוד להריץ. הקומפיילר מדווח CS0144 ("Cannot create an instance of the abstract type or interface"). צרו במקום זאת מופע של מחלקה יורשת קונקרטית, ואם רוצים, שמרו אותו במשתנה מהטיפוס האבסטרקטי: Shape s = new Circle(2);.

איור של שפות התכנות ב-Coddy

ללמוד תכנות עם Coddy

להתחיל