מפת הלוגיקה של שאלוני קוד


אילו הבחנות חוזרות שוב ושוב בשאלות על הורשה ופולימורפיזם

לאחר מעבר על שאלוני הקוד שב-interactive/ חוזרות כמעט תמיד אותן תחנות החלטה. מי שפותר לפי סדר קבוע לא נופל בקלות במלכודות של override, overload, new, cast, ו-protected.

שלושת החוקים שפותחים כל שאלה

  1. קודם מזהים את הטיפוס המוצהר של המשתנה שעליו קוראים לפעולה.
  2. אחר כך מזהים את הטיפוס בזכרון של האובייקט בפועל.
  3. רק אז מחליטים האם מדובר ב-overload, ב-override, ב-hiding, ב-cast, או בכלל בשגיאת קומפילציה עוד לפני זמן הריצה.

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

הקטגוריות שחוזרות בשאלונים

קטגוריה מה צריך לבדוק מה בדרך כלל קובע את התשובה
טיפוס מוצהר מול טיפוס בזכרון מי המשתנה שעליו קוראים לפעולה, ועל איזה אובייקט הוא מצביע בפועל קומפילציה לפי הטיפוס המוצהר, פולימורפיזם בזמן ריצה לפי הטיפוס בזכרון
התאמת חתימה האם בכלל קיימת פעולה עם אותו שם ועם פרמטרים מתאימים אם אין חתימה מתאימה, נעצרים עם שגיאת קומפילציה
overload אותו שם פעולה, חתימות שונות הבחירה נעשית בזמן קומפילציה לפי הטיפוסים המוצהרים של הארגומנטים
override אותה חתימה בדיוק, בשרשרת virtual/override אחרי שנבחרה החתימה, ההפעלה נקבעת לפי הטיפוס בזכרון
hiding עם new שם זהה, אבל אין דריסה אלא הסתרה אין כאן פולימורפיזם דינמי; מה שייקרא תלוי במה שרואים דרך הטיפוס המוצהר
cast והשמה האם ההמרה מותרת תחבירית, והאם האובייקט בפועל מתאים לה המרה יכולה להתקמפל אבל להיכשל בזמן ריצה עם InvalidCastException
הרשאות גישה private, protected, public לעתים השאלה נופלת כבר בקומפילציה כי החבר לא נגיש
בנאים וסדר אתחול מי קורא ל-base(...), מתי משתנים מאותחלים חשוב במיוחד כשבנאי של האב קורא לפעולה וירטואלית או כששדות מקבלים ערך דרך שרשרת בנאים
שגיאות זמן ריצה נוספות null, מערכים קו-וריאנטיים, גישה לעצם לא מתאים הקוד מתקמפל, אבל נופל רק בזמן הרצה
המרה שמתקמפלת אך נכשלת בזמן ריצה הדגמה של InvalidCastException כאשר B יורש מ-A ✓ עובד A obj = new B() אובייקט B בזמן ריצה B b = (B)obj; // ✓ מצליח obj הוא B — המרה תקינה ✗ נכשל בזמן ריצה A obj = new A() אובייקט A בזמן ריצה B b = (B)obj; // מתקמפל ✓ InvalidCastException ✗ למה המהדר מאשר? A (הורה) B (ילד) C (ילד) משתנה מטיפוס A יכול להצביע על A, B, או C — המהדר לא יודע מה האובייקט האמיתי, אז הוא מאשר את ההמרה ל-B. רק CLR בזמן ריצה בודק: האם האובייקט הוא באמת B? פתרונות בטוחים b is B obj as B if (obj is B b) { ... }

סדר פתרון מומלץ בבחינה

  1. סמנו ליד כל משתנה את הטיפוס המוצהר ואת הטיפוס בזכרון.
  2. אם יש cast, בדקו קודם אם הוא חוקי תחבירית, ואז האם הוא באמת יכול להצליח בזמן ריצה.
  3. חפשו רק פעולות שהטיפוס המוצהר מכיר: המחלקה של המשתנה והאבות שלה.
  4. בחרו חתימה מתאימה. זהו שלב של overload, ולכן הוא עדיין שלב קומפילציה.
  5. אם הפעולה שנבחרה היא virtual/override, עברו לטיפוס בזכרון כדי להחליט איזו גרסה תרוץ.
  6. אם הפעולה שנבחרה אינה וירטואלית, או שמדובר ב-new, הישארו עם הטיפוס המוצהר.
  7. רק בסוף חשבו על הפלט, על ערכי השדות, ועל שגיאות זמן ריצה אפשריות.

צורת רישום

Variable to heap object memory diagram Declared type variable with circle label pointing to runtime object with type circle and property values A a1 = new A() { x = -1 } A a1 A x 1- A a2 = new B() { x = 99 } A a2 B x 99

מלכודות קלאסיות מן השאלונים

משפטים שגויים שתלמידים אומרים לעצמם:

  • “אם האובייקט בזכרון הוא Beaver, תמיד תרוץ פעולה של Beaver”. לא נכון. קודם צריך שהחתימה תיבחר בכלל דרך הטיפוס המוצהר.
  • new זה כמו override”. לא נכון. new מסתיר; הוא לא ממשיך את שרשרת הדריסה.
  • cast משנה את האובייקט בזכרון”. לא נכון. cast משנה את מה שהקומפיילר והרץ מתייחסים אליו, אבל לא יוצר אובייקט חדש ולא מחליף טיפוס אמיתי.
  • “אם יש override, כבר לא צריך לבדוק חתימה”. לא נכון. בחירת החתימה קודמת לדריסה.

משפט מפתח אחד שכדאי לזכור

overload נבחר לפי טיפוס מוצהר. override נבחר לפי טיפוס בזכרון. new נשאר לפי טיפוס מוצהר. cast נבדק גם בקומפילציה וגם בזמן ריצה.

דפי המשך