מה זה?

משתנה בודד מחזיק ערך אחד — אבל מה קורה כשצריך לשמור 50 שמות של תלמידים? להצהיר 50 משתנים נפרדים (name1, name2, …) זה בלתי אפשרי לתחזוקה. מערכים (array) ו-List<T> פותרים את זה: שני סוגי אוסף שמחזיקים כמה ערכים תחת “משתנה” אחד — אבל עם הבדל קריטי: מערך נעול בגודלו מרגע היצירה, בעוד List<T> יכול לגדול ולהתכווץ דינמית.

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

• Array (int[]) — אוסף בגודל קבוע, נקבע ברגע היצירה ולא ניתן לשינוי

• List<T> — אוסף דינמי; אפשר להוסיף (Add) ולהסיר (Remove) איברים בזמן ריצה

• Index — המיקום של איבר באוסף, מתחיל תמיד מ-0

• Length (למערך) / Count (ל-List<T>) — כמה איברים יש כרגע באוסף

• IndexOutOfRangeException — שגיאת ריצה כשמנסים לגשת לאינדקס שלא קיים באוסף

// Array — fixed size from the moment of creation
int[] scores = new int[3] { 90, 85, 78 };
Console.WriteLine(scores[0]);       // 90 — index starts from 0
Console.WriteLine(scores.Length);   // 3
// scores[3] = 100;                 // IndexOutOfRangeException — there's no fourth slot

// List<T> — grows and shrinks at runtime
List<string> names = new List<string> { "Dana", "Avi" };
names.Add("Rotem");        // now 3 items
names.Remove("Avi");      // now 2 items — "Dana", "Rotem"
Console.WriteLine(names.Count);   // 2
// foreach — same syntax for both types, exactly
foreach (string name in names)
{
    Console.WriteLine(name);
}

הסבר עיקרי

למה בכלל להשתמש במערך אם List<T> גמיש יותר — מערך תופס פחות זיכרון וריצה מהירה מעט יותר, כי הגודל שלו ידוע וקבוע מראש; ל-List<T> יש overhead ניהולי כדי לתמוך בגדילה דינמית. בפועל: אם יודעים בוודאות שהכמות לא תשתנה (למשל “7 ימים בשבוע”), מערך מתאים. בכל מצב אחר — במיוחד כשלא ברור מראש כמה איברים יהיו — List<T> הוא הבחירה הנכונה כמעט תמיד.

Add/Remove הם מה ש-List<T> נותן ומערך לא — זו בדיוק הסיבה ש-List<T> נפוץ הרבה יותר בקוד אמיתי: רשימת משימות שמשתמש מוסיף ומוחק ממנה, תוצאות שמצטברות תוך כדי ריצה — כל אלה דורשים גודל שלא ידוע מראש.

Index תמיד מתחיל מ-0 — האיבר הראשון הוא [0], לא [1]. זו טעות נפוצה במיוחד למי שבא משפות/הקשרים אחרים — וגישה לאינדקס שלא קיים (למשל scores[3] על מערך בגודל 3) זורקת IndexOutOfRangeException בזמן ריצה, לא שגיאת קומפילציה.

יתרונות

מערך יעיל בזיכרון ובביצועים כשהגודל קבוע וידוע; List<T> נותן גמישות מלאה להוספה/הסרה בלי לדעת מראש כמה איברים יהיו; שניהם תומכים ב-foreach באותו תחביר בדיוק, כך שקל להחליף ביניהם.

חסרונות

מערך לא ניתן לשינוי גודל אחרי היצירה — צריך ליצור מערך חדש ולהעתיק אם רוצים “להוסיף” איבר; List<T> עם overhead ניהולי קטן שגורם לו להיות מעט פחות יעיל ממערך בתרחישי ביצועים קריטיים מאוד.

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

• מערך — גודל קבוע מרגע היצירה; List<T> — גודל דינמי, תומך ב-Add/Remove

• אינדקס תמיד מתחיל מ-0; אינדקס לא קיים זורק IndexOutOfRangeException בזמן ריצה

• Length שייך למערך, Count שייך ל-List<T> — שמות שונים לאותו רעיון

• List<T> היא ה-Generic הראשון שנתקלים בו — T הוא placeholder לטיפוס (List<string>, List<int>)

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

• לנסות “להוסיף” איבר למערך קיים ישירות (array[3] = x על מערך בגודל 3) — צריך מערך גדול יותר או List<T>

• לבלבל בין Length (מערך) ל-Count (List<T>) ולקבל שגיאת קומפילציה

• לגשת לאיבר לפי אינדקס בלי לבדוק שהוא בטווח, ולקבל IndexOutOfRangeException בזמן ריצה בלתי צפוי

סיכום

מערך ו-List<T> שניהם מחזיקים כמה ערכים תחת משתנה אחד, אבל מערך נעול בגודל קבוע מרגע היצירה בעוד List<T> גדל ומתכווץ דינמית עם Add/Remove. כברירת מחדל, כשלא ברור מראש כמה איברים יהיו — List<T> הוא הבחירה הנכונה; מערך שמור למקרים שבהם הגודל קבוע וידוע מראש.

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

Microsoft Learn — Arrays

Microsoft Learn — List<T>


תרגילים

תרגיל 1 — סכום מערך

המשימה: צרו מערך int[] עם 5 מספרים, וחשבו עם foreach את סכומם הכולל.

בדיקה: המערך {1, 2, 3, 4, 5} מדפיס סכום 15.

תרגיל 2 — ניהול רשימת קניות

המשימה: צרו List<string> ריקה, הוסיפו 4 פריטים עם Add, הסירו אחד עם Remove, והדפיסו את הרשימה הסופית עם foreach.

בדיקה: הרשימה הסופית מכילה 3 פריטים — כל מה שנוסף, פחות מה שהוסר.


פרויקט מסכם

המשימה: כתבו תוכנית “ניהול תלמידים” שמאחסנת ציונים ומחשבת עליהם סטטיסטיקות בסיסיות.

דרישות:

  1. List<int> שמתחילה ריקה, וממלאים אותה בלולאה עם 5 ציונים מהמשתמש (Console.ReadLine + int.TryParse)
  2. חישוב ממוצע (Sum() חלקי Count), ציון מקסימלי ומינימלי (מותר שימוש ב-Max()/Min() אם מוכרים, או חישוב ידני עם לולאה)
  3. הדפסת כל שלוש הסטטיסטיקות בסיום

בדיקה: קלט {80, 90, 70, 100, 60} מדפיס ממוצע 80, מקסימום 100, מינימום 60.

מה בפרק הבא

בפרק הבא — פונקציות: איך אורזים לוגיקה חוזרת לבלוק אחד שקוראים לו בשם, במקום להעתיק-הדבק את אותו קוד בכמה מקומות.