# Generics📖 שיעור

## מה זה?

כבר השתמשנו ב-`List<T>` ו-`Dictionary<TKey, TValue>` לאורך הקורס — הם עבדו על `int`, `string`, `Student` בלי שום שינוי בקוד שלהם. איך זה אפשרי? הם כתובים עם **Generics** — קוד שכותבים **פעם אחת**, עם `T` כ"placeholder לטיפוס", ו-C# ממלאת אותו בטיפוס האמיתי בכל שימוש. בלי Generics, היינו צריכים `IntStack`, `StringStack`, `StudentStack` — מחלקה נפרדת לכל טיפוס, עם אותו קוד בדיוק פרט לשם הטיפוס.

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

• `T` (Type Parameter) — placeholder לטיפוס, מוחלף בטיפוס האמיתי בזמן קומפילציה בכל שימוש קונקרטי

• Generic Class — מחלקה שמוגדרת עם `<T>`, וכל שימוש בה בוחר טיפוס ספציפי (`Stack<int>`, `Stack<string>`)

• `Dictionary<TKey, TValue>` — אוסף Generic עם שני Type Parameters: מפתח וערך

• Type Safety — הקומפיילר בודק ש-`Stack<int>` מכיל רק `int`, בדיוק כמו כל טיפוס אחר — אין "ערבוב" אפשרי

```csharp
// Generic class — written once, for every type
class Box<T>
{
    private T _item;
    public void Set(T item) => _item = item;
    public T Get() => _item;
}

Box<int> intBox = new Box<int>();
intBox.Set(42);
Console.WriteLine(intBox.Get());   // 42

Box<string> stringBox = new Box<string>();
stringBox.Set("Hello");
Console.WriteLine(stringBox.Get());   // Hello
// intBox.Set("text");   // compile error — Box<int> only accepts int
```

```csharp
// Dictionary<TKey, TValue> — two Type Parameters
Dictionary<string, int> ages = new Dictionary<string, int>();
ages["Dana"] = 25;
ages["Avi"] = 30;

if (ages.TryGetValue("Dana", out int age))
{
    Console.WriteLine($"Age: {age}");   // 25
}
```

```csharp
// Custom generic method
static T FindMax<T>(List<T> items) where T : IComparable<T>
{
    T max = items[0];
    foreach (T item in items)
    {
        if (item.CompareTo(max) > 0) max = item;
    }
    return max;
}

int maxNumber = FindMax(new List<int> { 3, 7, 2, 9, 4 });   // 9
```

## הסבר עיקרי

למה לא סתם להשתמש ב-`object` במקום `T` — אפשר היה לכתוב `Box` עם `object` פנימי במקום `T`, שיכול "להכיל הכל". אבל אז מאבדים את בדיקת הטיפוסים — `object` יכול להיות כל דבר, כולל טעות (לשים `int` ולשלוף כ-`string` וזה יקרוס בזמן ריצה). `T` שומר על Type Safety מלאה: `Box<int>` **תמיד** מכיל `int`, נבדק בקומפילציה, בדיוק כמו כל טיפוס רגיל.

`where T : IComparable<T>` — constraint (אילוץ) — לפעמים מתודה Generic צריכה **לדעת** משהו על `T` כדי לפעול (כמו `FindMax` שצריך להשוות ערכים). ה-`where` מגביל אילו טיפוסים מותר להשתמש כ-`T` — כאן: רק טיפוסים שמממשים `IComparable<T>` (בדיוק מה שתרגלנו בשיעור ה-Interfaces). בלי ה-constraint, `item.CompareTo(max)` בכלל לא היה מתקמפל — C# לא הייתה יודעת שכל `T` אפשרי תומך בהשוואה.

`Dictionary<TKey, TValue>` הוא בדיוק אותו רעיון עם שני Type Parameters — כמו `List<T>` עם `T` אחד, `Dictionary` מקבל שניים: `TKey` (סוג המפתח) ו-`TValue` (סוג הערך). כל שימוש קונקרטי (`Dictionary<string, int>`) בוחר טיפוסים ספציפיים, וה-Dictionary הגנרי (שנכתב פעם אחת ב-.NET) עובד זהה לכל שילוב.

## יתרונות

Generics חוסכים שכפול קוד בין טיפוסים דומים; שומרים על Type Safety מלאה — שגיאות טיפוס נתפסות בקומפילציה, לא בזמן ריצה; `where` constraints מאפשרים למתודה Generic להשתמש בהתנהגות ספציפית (כמו `CompareTo`) כשהיא נחוצה.

## חסרונות

תחביר Generic עם constraints מרובים יכול להיות מסורבל לקריאה; שגיאות קומפילציה על קוד Generic (במיוחד מורכב) לפעמים ארוכות ומבלבלות יותר מקוד רגיל.

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

• `T` הוא placeholder לטיפוס, נקבע בזמן שימוש קונקרטי (`Box<int>`, `Box<string>`)

• Generics שומרים Type Safety מלאה — לא כמו `object` שמאבד בדיקת טיפוסים

• `where T : IComparable<T>` הוא constraint — מגביל אילו טיפוסים מותר להשתמש כ-`T`

• `List<T>`, `Dictionary<TKey,TValue>` הם Generics שכבר בשימוש לאורך כל הקורס

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

• להשתמש ב-`object` במקום Generics "כי זה יותר גמיש" — מאבד Type Safety ומוסיף המרות (casting) מסוכנות

• לנסות להשתמש במתודה שדורשת `IComparable` על `T` בלי constraint מתאים — שגיאת קומפילציה

• לבלבל בין `T` (Type Parameter, בזמן קומפילציה) ל-`object` (טיפוס בסיס לכל דבר, בדיקה בזמן ריצה)

## סיכום

Generics מאפשרים לכתוב קוד (מחלקות ומתודות) פעם אחת שעובד על כל טיפוס, עם `T` כ-placeholder שנקבע בכל שימוש קונקרטי — תוך שמירה מלאה על בדיקת טיפוסים בקומפילציה. `List<T>` ו-`Dictionary<TKey, TValue>` הם דוגמאות מובנות ב-.NET; `where T : ...` מאפשר להגביל אילו טיפוסים תואמים, כשצריך להשתמש בהתנהגות ספציפית שלהם.

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

[Microsoft Learn — Generics](https://learn.microsoft.com/dotnet/csharp/fundamentals/types/generics)

[Microsoft Learn — Dictionary&lt;TKey,TValue&gt;](https://learn.microsoft.com/dotnet/api/system.collections.generic.dictionary-2)

---

## תרגילים

### תרגיל 1 — מחלקה Generic ראשונה

**המשימה:** כתבו `class Pair<T1, T2>` עם שני שדות, `First` ו-`Second`, וקונסטרוקטור שמקבל אותם. צרו מופע עם `Pair<string, int>`.

**בדיקה:** `Pair<string, int> p = new Pair<string, int>("דנה", 25);` מתקמפל ומדפיס נכון את שני השדות.

### תרגיל 2 — Dictionary בפועל

**המשימה:** בנו `Dictionary<string, List<string>>` שממפה שם מחלקה לרשימת תלמידים בה. הוסיפו 2 מחלקות עם תלמידים, והדפיסו את כולם עם `foreach` מקונן.

**בדיקה:** הפלט מציג כל מחלקה עם רשימת התלמידים הנכונה שלה.

---

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

**המשימה:** כתבו מחלקת `Repository<T>` גנרית פשוטה לניהול אוסף בזיכרון.

**דרישות:**
1. `class Repository<T>` עם `List<T>` פנימית, ומתודות `Add(T item)`, `GetAll()` (מחזירה `List<T>`), ו-`Count()` (מחזירה `int`)
2. שני שימושים שונים: `Repository<string>` לניהול שמות, ו-`Repository<int>` לניהול מספרים
3. הדגימו הוספה והדפסת התוכן בשני המקרים

**בדיקה:** אותה מחלקת `Repository<T>` בדיוק עובדת גם על `string` וגם על `int`, בלי שום שינוי בקוד שלה — Type Safety נשמרת בשני המקרים.

## מה בפרק הבא

בפרק הבא — **SOLID: SRP ו-OCP**: שני העקרונות הראשונים שמנחים איך לפצל אחריות בין מחלקות, ואיך להוסיף התנהגות חדשה בלי לשנות קוד קיים.
