# Exceptions לעומק — hierarchy וחריגות מותאמות אישית📖 שיעור

## מה זה?

ביחידת היסודות תפסנו סוגי חריגה ספציפיים (`FormatException`, `IndexOutOfRangeException`) בנפרד. אבל מה אם רוצים לתפוס "כל שגיאה שקשורה לקלט", בלי לכתוב `catch` נפרד לכל תת-סוג? ומה אם השגיאה היא לא טכנית אלא **עסקית** — כמו "יתרה לא מספיקה"? הפתרון: כל חריגות ה-.NET יורשות מ-`Exception` בהיררכיה אחת, ואפשר ליצור חריגות מותאמות-אישית משלנו שמייצגות בדיוק את השגיאות העסקיות של הדומיין שלנו.

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

• Exception Hierarchy — כל סוגי החריגות ב-.NET יורשים (בסופו של דבר) מ-`Exception`; `catch (Exception)` תופס הכל

• `catch` לפי סוג בסיס — תפיסת `catch (IOException)` תופס גם תתי-סוגים ספציפיים יותר שלה (כמו `FileNotFoundException`)

• Custom Exception — מחלקה שיורשת מ-`Exception` (או תת-סוג שלו), מייצגת שגיאה עסקית ספציפית לדומיין

• `throw` — זריקת חריגה חדשה באופן יזום, כשמצב עסקי לא תקין מתגלה

```csharp
// Hierarchy — a more general catch also catches more specific types
try
{
    // code that might throw FileNotFoundException, a subtype of IOException
}
catch (FileNotFoundException)
{
    Console.WriteLine("Specific file not found");   // most specific — checked first
}
catch (IOException)
{
    Console.WriteLine("More general I/O error");   // catches all other IO errors
}
catch (Exception)
{
    Console.WriteLine("Unexpected general error");   // last safety net
}
```

```csharp
// Custom Exception — a specific business error, not a technical one
class InsufficientFundsException : Exception
{
    public double RequestedAmount { get; }
    public double AvailableBalance { get; }

    public InsufficientFundsException(double requested, double available)
        : base($"Attempted to withdraw {requested}, but the available balance is only {available}")
    {
        RequestedAmount = requested;
        AvailableBalance = available;
    }
}

class BankAccount
{
    public double Balance { get; private set; }

    public void Withdraw(double amount)
    {
        if (amount > Balance)
            throw new InsufficientFundsException(amount, Balance);   // thrown deliberately
        Balance -= amount;
    }
}
```

```csharp
try
{
    var account = new BankAccount();
    account.Withdraw(500);
}
catch (InsufficientFundsException ex)
{
    Console.WriteLine($"{ex.Message} (missing {ex.RequestedAmount - ex.AvailableBalance})");
}
```

## הסבר עיקרי

סדר ה-`catch` קובע מה נתפס — C# בודקת בלוקי `catch` **מלמעלה למטה**, ותופסת ברגע שמצאה סוג תואם. לכן חייבים לסדר מהספציפי ביותר לכללי ביותר — `catch (Exception)` ראשון היה "בולע" הכל, ובלוקי ה-`catch` הספציפיים יותר אחריו לעולם לא היו רצים (וזו אפילו שגיאת קומפילציה ב-C# אם הכללי מגיע קודם).

Custom Exceptions הופכים שגיאות עסקיות למפורשות — `InsufficientFundsException` אומר בדיוק מה קרה, כולל נתונים רלוונטיים (`RequestedAmount`, `AvailableBalance`) שקוד תופס יכול להשתמש בהם — הרבה יותר שימושי מ-`throw new Exception("no money")` גנרי, שקשה לתפוס באופן ממוקד ולחלץ ממנו מידע מובנה.

`: base(message)` — בדיוק כמו קונסטרוקטור של מחלקה יורשת רגילה (מהיחידה הקודמת), `InsufficientFundsException` קורא ל-`base(...)` כדי להעביר את ה-`Message` הסטנדרטי ל-`Exception` הבסיסי — כך ש-`ex.Message` יעבוד כרגיל, בנוסף לשדות המותאמים-אישית.

## יתרונות

היררכיית חריגות מאפשרת תפיסה ברמת דיוק גמישה — ספציפי מאוד או כללי; Custom Exceptions הופכים שגיאות עסקיות למפורשות וניתנות-לזיהוי, עם נתונים רלוונטיים מובְנים; קוד קורא יכול לבחור להגיב אחרת לכל סוג חריגה עסקית ספציפי.

## חסרונות

יותר מדי סוגי Custom Exception לכל תרחיש קטן מוסיף מורכבות מיותרת; `catch (Exception)` כללי מדי בלי `catch` ספציפי לפניו מסתיר מידע חשוב על מה בדיוק השתבש.

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

• כל החריגות יורשות מ-`Exception`; `catch` לפי סוג בסיס תופס גם תתי-סוגים

• סדר בלוקי `catch` קריטי — מהספציפי לכללי, אחרת קוד לא מגיע (ואף שגיאת קומפילציה)

• Custom Exception יורש מ-`Exception`, קורא ל-`base(message)`, ויכול להוסיף שדות עסקיים משלו

• `throw` יזום זורק חריגה כשמצב עסקי (לא רק טכני) מתגלה כלא תקין

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

• לסדר `catch (Exception)` **לפני** `catch` ספציפי — שגיאת קומפילציה, כי הספציפי אף פעם לא יכול להיתפס

• להשתמש ב-`Exception` הגנרי במקום Custom Exception לשגיאות עסקיות חוזרות — מקשה על תפיסה ממוקדת

• לשכוח `: base(message)` בקונסטרוקטור של Custom Exception, ולאבד את ה-`Message` הסטנדרטי

## סיכום

כל חריגות ה-.NET יורשות מ-`Exception` בהיררכיה אחת — `catch` לפי סוג בסיס תופס גם תתי-סוגים ספציפיים יותר, וסדר בלוקי ה-`catch` (מהספציפי לכללי) קובע מה נתפס איפה. Custom Exceptions מאפשרים לייצג שגיאות עסקיות ספציפיות לדומיין (כמו `InsufficientFundsException`) עם נתונים מובְנים רלוונטיים, במקום `Exception` גנרי שקשה לתפוס ולפרש באופן ממוקד.

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

[Microsoft Learn — Creating and throwing exceptions](https://learn.microsoft.com/dotnet/standard/exceptions/how-to-create-user-defined-exceptions)

---

## תרגילים

### תרגיל 1 — היררכיית catch

**המשימה:** כתבו קוד עם שני בלוקי `catch` — `FormatException` ואחריו `Exception` כללי — שמנסה להמיר קלט לא תקין למספר.

**בדיקה:** קלט לא-מספרי נתפס על ידי `catch (FormatException)` הספציפי, לא על ידי הכללי שאחריו.

### תרגיל 2 — Custom Exception ראשון

**המשימה:** צרו `class InvalidAgeException : Exception` עם קונסטרוקטור שמקבל גיל לא תקין ומעביר הודעה מתאימה ל-`base`. זרקו אותה ממתודה `SetAge(int age)` אם `age < 0`.

**בדיקה:** `SetAge(-5)` זורקת `InvalidAgeException` עם הודעה ברורה שנתפסת ומודפסת בהצלחה.

---

## פרויקט מסכם

**המשימה:** בנו מחלקת `Inventory` (מלאי) עם Custom Exception עסקי.

**דרישות:**
1. `class OutOfStockException : Exception` עם שדות `ProductName` ו-`RequestedQuantity`, וקונסטרוקטור עם `base(message)` מתאים
2. `class Inventory` עם `Dictionary<string, int>` פנימי (מוצר → כמות), ומתודת `RemoveStock(string product, int quantity)` שזורקת `OutOfStockException` אם הכמות המבוקשת חורגת מהמלאי
3. קוד ב-`Main` שתופס את החריגה ומדפיס הודעה מפורטת עם שני השדות המותאמים-אישית

**בדיקה:** בקשת כמות תקינה מצליחה ומעדכנת את המלאי; בקשה חורגת זורקת `OutOfStockException` עם `ProductName`/`RequestedQuantity` נכונים, ונתפסת בהצלחה.

## מה בפרק הבא

בפרק הבא — **קבצים (File I/O)**: איך קוראים וכותבים לקבצים על הדיסק, כולל שילוב עם `try`/`catch`/`finally` לטיפול נכון במשאבים חיצוניים.
