מה זה?

כדי לסנן את התלמידים הבוגרים מ-List<Student>, הדרך “הידנית” היא לולאת foreach, if בפנים, ורשימה חדשה שממלאים ידנית — 5-6 שורות לפעולה פשוטה. LINQ (Language Integrated Query) פותר את זה בשורה אחת: students.Where(s => s.Age >= 18). זו לא רק תמציתיות — זו שפה משותפת וקריאה לפעולות שחוזרות שוב ושוב על כל אוסף: סינון, מיפוי, מיון.

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

• LINQ — סט מתודות מובנות על כל IEnumerable<T> (כולל List<T>, מערכים) לסינון, מיפוי ומיון

• Lambda Expression (s => s.Age >= 18) — פונקציה אנונימית קצרה: “עבור כל s, החזר את…”; הדרך הנפוצה להעביר לוגיקה ל-LINQ

• Where — מסנן אוסף, מחזיר רק איברים שעונים על תנאי

• Select — ממפה כל איבר לערך אחר (טרנספורמציה), לא בהכרח מאותו טיפוס

• OrderBy / OrderByDescending — ממיין אוסף לפי מפתח, בלי לשנות את המקור

List<Student> students = new List<Student>
{
    new Student("Dana", 25), new Student("Avi", 17), new Student("Rotem", 30)
};

// Before LINQ — manual
List<Student> adultsManual = new List<Student>();
foreach (var s in students)
{
    if (s.Age >= 18) adultsManual.Add(s);
}

// With LINQ — one line
List<Student> adults = students.Where(s => s.Age >= 18).ToList();
// Select — maps to another type (here: from Student to string)
List<string> names = students.Select(s => s.Name).ToList();
// { "Dana", "Avi", "Rotem" }

// Combining Where + Select — filter, then map, method chaining
List<string> adultNames = students.Where(s => s.Age >= 18).Select(s => s.Name).ToList();
// OrderBy / OrderByDescending — sorting without changing the source
List<Student> byAgeAsc = students.OrderBy(s => s.Age).ToList();
List<Student> byAgeDesc = students.OrderByDescending(s => s.Age).ToList();
Console.WriteLine(students.Count);   // still the original list, in the original order — OrderBy doesn't change it

הסבר עיקרי

Lambda Expression היא “פונקציה בשורה אחת” — s => s.Age >= 18 שקול לפונקציה bool IsAdult(Student s) { return s.Age >= 18; }, רק בלי להגדיר אותה בנפרד עם שם. ה-s משמאל לחץ הוא הפרמטר (השם שרירותי — יכול להיות כל אות/מילה), הביטוי מימין הוא מה שמוחזר. LINQ בנוי כולו על העברת לוגיקה קטנה כזו כארגומנט למתודות מובנות.

Where מול Select — Where מסנן (מחזיר תת-קבוצה מאותו טיפוס, פחות איברים); Select ממפה (מחזיר את אותו מספר איברים, אך אולי בטיפוס אחר לגמרי — Student → string). שרשור שניהם (Where(...).Select(...)) הוא דפוס נפוץ מאוד: קודם לסנן למה שרלוונטי, ואז לחלץ רק מה שצריך מכל איבר.

LINQ לא משנה את המקור — OrderBy (וכל שאר מתודות LINQ) תמיד מחזירות אוסף חדש — הרשימה המקורית (students) נשארת בדיוק כמו שהייתה. זה שונה מ-.Sort() (מהיחידה הקודמת, על IComparable) שכן משנה את הרשימה במקום. ההבדל הזה חשוב: LINQ בטוח יותר כי אי אפשר “לשבור” בטעות רשימה שקוד אחר עדיין מסתמך על הסדר המקורי שלה.

יתרונות

LINQ הופך פעולות סינון/מיפוי/מיון נפוצות לשורה אחת קריאה, במקום 5-6 שורות foreach ידניות; שרשור מתודות (Where(...).Select(...)) מבטא זרימת עיבוד נתונים בבירור; לא משנה את האוסף המקורי — בטוח יותר מ-.Sort().

חסרונות

שרשור ארוך מדי (5+ מתודות ברצף) יכול להיות קשה לקריאה כמו קוד ידני מסורבל; LINQ מריץ את הביטוי על כל איבר בכל קריאה — לולאה ידנית ממוטבת-יד עשויה להיות מעט מהירה יותר בתרחישי ביצועים קריטיים מאוד.

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

• Lambda (s => expr) היא פונקציה אנונימית קצרה — הדרך הנפוצה להעביר לוגיקה ל-LINQ

• Where מסנן (פחות איברים, אותו טיפוס); Select ממפה (אותו מספר איברים, טיפוס אפשרי אחר)

• LINQ תמיד מחזיר אוסף חדש — לא משנה את המקור, בניגוד ל-.Sort()

• .ToList() בסוף שרשור LINQ ממיר את התוצאה (שהיא IEnumerable<T>) בחזרה ל-List<T> קונקרטית

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

• לשכוח .ToList() בסוף ולנסות להשתמש בתוצאה כאילו היא List<T> ישירות (זה IEnumerable<T> עד שממירים)

• לצפות ש-OrderBy ישנה את הרשימה המקורית — הוא לא, מחזיר עותק ממוין חדש

• לכתוב שרשור LINQ ארוך מדי ובלתי-קריא במקום לפרק לצעדים עם שמות ברורים

סיכום

LINQ הופך פעולות אוסף נפוצות (סינון, מיפוי, מיון) לשורה אחת קריאה במקום לולאות foreach ידניות. Lambda Expressions (s => expr) הן הדרך להעביר לוגיקה קצרה ל-Where/Select/OrderBy. חשוב לזכור: LINQ תמיד מחזיר אוסף חדש, לא משנה את המקור, ו-.ToList() נדרש כדי להמיר תוצאה בחזרה ל-List<T> קונקרטית לשימוש נוסף.

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

Microsoft Learn — LINQ overview


תרגילים

תרגיל 1 — Where

המשימה: מתוך List<int> עם 10 מספרים, סננו רק את הזוגיים עם Where והדפיסו את התוצאה.

בדיקה: רק המספרים הזוגיים מהרשימה המקורית מופיעים בתוצאה.

תרגיל 2 — Select ושרשור

המשימה: מתוך List<Student>, הפיקו List<string> עם משפט תיאור לכל תלמיד ("דנה, גיל 25") בעזרת Select, אך רק עבור תלמידים בגילאי 18+ (שילוב עם Where).

בדיקה: הרשימה המתקבלת מכילה רק תלמידים בגירים, כל אחד כמחרוזת מתוארת נכון.


פרויקט מסכם

המשימה: בנו “לוח תוצאות” למבחן — מתוך List<Student> עם ציון (Score), הפיקו דוח ממוין.

דרישות:

  1. סננו עם Where רק תלמידים שעברו (Score >= 60)
  2. מיינו את התוצאה עם OrderByDescending לפי ציון — הגבוה ביותר ראשון
  3. הפיקו עם Select רשימת מחרוזות בפורמט "1. דנה — 95" (כולל מיקום בדירוג), והדפיסו את כולן

בדיקה: רק תלמידים שעברו מופיעים; הרשימה ממוינת מהציון הגבוה לנמוך; המיקום בדירוג (1, 2, 3…) נכון עבור כל תלמיד.

מה בפרק הבא

בפרק הבא — LINQ, חלק ב’: GroupBy לקיבוץ נתונים לפי מפתח, ו-FirstOrDefault לשליפה בטוחה של איבר בודד — הכלים האחרונים לקראת הפרויקט המסכם.