# Nullable Types📖 שיעור

## מה זה?

איך מייצגים "גיל לא ידוע" במשתנה מסוג `int`? `int` הוא Value Type — הוא **חייב** תמיד להכיל מספר כלשהו, ואין ערך "ריק" מובנה כמו ש-`string` יכול להיות `null`. שימוש בערך שרירותי כמו `-1` או `0` כדי לסמן "לא ידוע" מסוכן — מה אם `0` הוא גיל תקין? Nullable Types (`int?`) פותרים את זה במפורש: מרחיבים טיפוס פרימיטיבי כך שיוכל להכיל גם `null`, בלי לאבד את בדיקת הטיפוסים של C#.

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

• `int?` (או `Nullable<int>`) — גרסה של `int` שיכולה גם להכיל `null`, בנוסף לכל ערך מספרי רגיל

• `.HasValue` — בודק אם משתנה Nullable מכיל ערך אמיתי (`true`) או `null` (`false`)

• `.Value` — שולף את הערך בפועל מתוך משתנה Nullable; זורק חריגה אם קוראים לו כש-`HasValue` הוא `false`

• `??` (Null-Coalescing Operator) — "אם השמאלי `null`, השתמש בימני במקום"

• `?.` (Null-Conditional Operator) — קורא למתודה/מאפיין רק אם המשתנה לא `null`; אחרת מחזיר `null` בלי לזרוק חריגה

```csharp
int? age = null;         // valid! int? can hold null
int regularAge = 25;     // regular int — never null

if (age.HasValue)
{
    Console.WriteLine($"Age: {age.Value}");
}
else
{
    Console.WriteLine("Age unknown");
}
```

```csharp
// ?? — default value if null
int? userAge = null;
int displayAge = userAge ?? 0;   // if userAge is null, use 0 instead
Console.WriteLine(displayAge);   // 0

// ?. — safe call that prevents NullReferenceException
string name = null;
int? length = name?.Length;   // null, without throwing an exception
Console.WriteLine(length ?? -1);   // -1, because length is null
```

## הסבר עיקרי

למה `0`/`-1` הם פתרון מסוכן — ערך "מוסכם" לייצוג ריק תלוי בזיכרון של כל מפתח בצוות שידע את המוסכמה, ומתנגש עם ערכים תקינים אמיתיים (גיל `0` לתינוק, יתרה `0` בחשבון). `int?` פותר את זה בצורה מפורשת ובטוחה בטיפוסים — "אין ערך" הוא מצב נבדק, לא מספר מוסכם.

`HasValue`/`Value` מול `??`/`?.` — `HasValue` ו-`Value` נותנים שליטה מלאה עם בדיקה מפורשת, אבל דורשים `if` בכל פעם. `??` ו-`?.` הם קיצורי דרך נפוצים לתרחישים פשוטים: "תן לי ערך ברירת מחדל אם null" (`??`), או "קרא למתודה רק אם לא null, אחרת תחזיר null בלי לקרוס" (`?.`). בקוד אמיתי, `??`/`?.` נפוצים בהרבה כי הם קצרים וקריאים יותר לרוב המקרים.

הקשר ל-`NullReferenceException` — קריאה למאפיין/מתודה על משתנה `null` (בלי `?.`) היא אחת השגיאות הכי נפוצות ב-C#, ותוזכר שוב בשיעור על Exceptions. `?.` הוא בדיוק הכלי שמונע אותה מראש כשמדובר בגישה שרשרתית שעלולה להיתקל ב-`null` באמצע.

## יתרונות

`int?`/`Nullable<T>` מייצג "אין ערך" בבירור, בלי מוסכמות מספריות מסוכנות; `??` נותן ברירת מחדל בשורה אחת קצרה; `?.` מונע `NullReferenceException` בגישה שרשרתית בלי `if` מפורש בכל שלב.

## חסרונות

`int?` תופס מעט יותר זיכרון מ-`int` רגיל (יש בו דגל נוסף שמציין אם יש ערך); שרשרת ארוכה מדי של `?.` עלולה להסתיר לוגיקה עסקית חשובה ("למה זה בכלל יכול להיות null כאן?") מאחורי תחביר נוח מדי.

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

• `int?` = `Nullable<int>` — מרחיב טיפוס Value להכיל `null`

• `.HasValue`/`.Value` הם הבדיקה/השליפה המפורשות; `??`/`?.` הם קיצורי דרך נפוצים

• `??` נותן ערך ברירת מחדל; `?.` מונע `NullReferenceException` בגישה שעלולה להיתקל ב-null

• קריאה ל-`.Value` כש-`HasValue` הוא `false` זורקת `InvalidOperationException`

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

• להשתמש ב-`-1`/`0` כמוסכמה ל"אין ערך" במקום `int?` — פגיע ומבלבל

• לקרוא ל-`.Value` בלי לבדוק `.HasValue` קודם — עלול לזרוק חריגה

• לשכוח שגם `??`/`?.` צריך להשתמש בהם באופן מודע, לא "לזרוק" בכל מקום בלי לחשוב אם `null` הגיוני שם

## סיכום

`int?` (ו-Nullable Types באופן כללי) מרחיבים טיפוס Value להכיל `null`, ומחליפים מוסכמות מספריות מסוכנות ("0 פירושו לא ידוע") בייצוג מפורש ובטוח-טיפוסים ל"אין ערך". `HasValue`/`Value` נותנים שליטה מפורשת; `??` ו-`?.` הם קיצורי דרך נוחים למקרים הנפוצים ביותר — ברירת מחדל וגישה בטוחה.

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

[Microsoft Learn — Nullable value types](https://learn.microsoft.com/dotnet/csharp/language-reference/builtin-types/nullable-value-types)

[Microsoft Learn — Null-conditional operators](https://learn.microsoft.com/dotnet/csharp/language-reference/operators/member-access-operators#null-conditional-operators--and-)

---

## תרגילים

### תרגיל 1 — int? בסיסי

**המשימה:** הצהירו על `int? temperature = null`, בדקו עם `HasValue` והדפיסו "אין נתון" אם `null`, אחרת הדפיסו את הערך.

**בדיקה:** כשהערך `null`, מודפס "אין נתון"; כשמאתחלים ל-`25`, מודפס `25`.

### תרגיל 2 — ?? לערך ברירת מחדל

**המשימה:** כתבו `int? score = null;` והדפיסו את `score ?? -1`. שנו את `score` לערך אמיתי (`85`) והדפיסו שוב.

**בדיקה:** כש-`score` הוא `null`, מודפס `-1`; כש-`score` הוא `85`, מודפס `85`.

---

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

**המשימה:** כתבו מחלקת עזר `UserProfile` פשוטה (או פשוט קוד ב-`Main`) שמנהלת פרטי משתמש עם שדות אופציונליים.

**דרישות:**
1. משתנים: `string name` (חובה), `int? age` (אופציונלי), `double? salary` (אופציונלי)
2. פונקציה `PrintProfile` שמדפיסה את הפרופיל — אם `age`/`salary` הם `null`, מדפיסה "לא צוין" במקומם (עם `??` או בדיקה מפורשת)
3. הדגימו קריאה אחת עם כל השדות מלאים, וקריאה אחת עם `age` ו-`salary` שניהם `null`

**בדיקה:** פרופיל מלא מדפיס את כל הערכים כרגיל; פרופיל עם `null` ב-`age`/`salary` מדפיס "לא צוין" בשדות האלה בלבד, בלי לקרוס.

## מה בפרק הבא

בפרק הבא — **טיפול בשגיאות (Exceptions) בסיסי**: מה קורה כשקוד נכשל בזמן ריצה (כמו קריאה ל-`.Value` על Nullable ריק), ואיך תופסים את זה בעדינות עם `try`/`catch` במקום שהתוכנית פשוט תקרוס.
