מערכים מול List<T>
מה זה?
משתנה בודד מחזיק ערך אחד — אבל מה קורה כשצריך לשמור 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> הוא הבחירה הנכונה; מערך שמור למקרים שבהם הגודל קבוע וידוע מראש.
דוקומנטציה רשמית
תרגילים
תרגיל 1 — סכום מערך
המשימה: צרו מערך int[] עם 5 מספרים, וחשבו עם foreach את סכומם הכולל.
בדיקה: המערך {1, 2, 3, 4, 5} מדפיס סכום 15.
תרגיל 2 — ניהול רשימת קניות
המשימה: צרו List<string> ריקה, הוסיפו 4 פריטים עם Add, הסירו אחד עם Remove, והדפיסו את הרשימה הסופית עם foreach.
בדיקה: הרשימה הסופית מכילה 3 פריטים — כל מה שנוסף, פחות מה שהוסר.
פרויקט מסכם
המשימה: כתבו תוכנית “ניהול תלמידים” שמאחסנת ציונים ומחשבת עליהם סטטיסטיקות בסיסיות.
דרישות:
List<int>שמתחילה ריקה, וממלאים אותה בלולאה עם 5 ציונים מהמשתמש (Console.ReadLine+int.TryParse)- חישוב ממוצע (
Sum()חלקיCount), ציון מקסימלי ומינימלי (מותר שימוש ב-Max()/Min()אם מוכרים, או חישוב ידני עם לולאה) - הדפסת כל שלוש הסטטיסטיקות בסיום
בדיקה: קלט {80, 90, 70, 100, 60} מדפיס ממוצע 80, מקסימום 100, מינימום 60.
מה בפרק הבא
בפרק הבא — פונקציות: איך אורזים לוגיקה חוזרת לבלוק אחד שקוראים לו בשם, במקום להעתיק-הדבק את אותו קוד בכמה מקומות.