List<T> הוא אוסף מסודר שגדל וקטן כשמוסיפים ומוחקים פריטים. הוא נמצא ב-System.Collections.Generic, עובד עם אינדקסים כמו מערך (list[0]), והוא הבחירה הרגילה ב-C# עבור "הרבה דברים".
יצירת רשימה והוספת פריטים
פרמטר הטיפוס שבסוגריים המשולשים אומר מה הרשימה מחזיקה: List<string>, List<int>, List<Order>.
פלט:
2
Zoe, Ana, Ben, Chloe, Dev
Ana
Ana B.
3
Count הוא מאפיין, לא מתודה (מערכים משתמשים ב-Length, ל-LINQ יש מתודה Count(); ברשימה השתמשו במאפיין). Insert מזיז כל פריט שאחריו מקום אחד קדימה, ולכן הכנסה לתחילת רשימה גדולה לוקחת זמן ביחס לגודל שלה. Add לסוף היא הפעולה הזולה.
מחיקת פריטים
יש ארבע דרכים למחוק, לפי מה שאתם יודעים על הפריט:
פלט:
True: 85, 62, 40, 91, 55
False
62, 40, 91, 55
2 removed: 62, 91
Caught ArgumentOutOfRangeException
0
Remove מקבל ערך ומוחק רק את ההתאמה הראשונה. כדי למחוק את כל העותקים של ערך, השתמשו ב-RemoveAll(s => s == 40). אינדקס שגוי ברשימה זורק ArgumentOutOfRangeException (מערך זורק במקום זאת IndexOutOfRangeException), גם עבור list[i] וגם עבור RemoveAt(i). RemoveRange(index, count) מוחק בלוק.
Count מול Capacity
רשימה שומרת את הפריטים שלה במערך פנימי. Capacity הוא הגודל של המערך הזה; Count הוא כמה משבצות בשימוש. כש-Count מגיע ל-Capacity, ה-Add הבא מקצה מערך בגודל כפול ומעתיק אליו הכול.
פלט:
Count 0, Capacity 0
Count 1, Capacity 4
Count 2, Capacity 4
Count 3, Capacity 4
Count 4, Capacity 4
Count 5, Capacity 8
Count 6, Capacity 8
Count 7, Capacity 8
Count 8, Capacity 8
Count 9, Capacity 16
Count 0, Capacity 1000
ההכפלה אומרת שרוב הקריאות ל-Add הן רק כתיבה למשבצת פנויה. אם אתם יודעים בערך כמה פריטים יגיעו, העבירו את המספר לבנאי כדי לדלג על ההעתקות שבדרך. שימו לב של-new List<int>(1000) עדיין יש Count של 0: list[0] עליה זורק חריגה. Capacity הוא מקום, לא פריטים.
חיפוש: Contains, IndexOf, Find, Exists
פלט:
True
2
-1
32.00
2 cheap items
False
2
True
False
Find מחזיר את ההתאמה הראשונה, או את ערך ברירת המחדל של הטיפוס (null למחלקה) כששום דבר לא מתאים, ולכן בדקו לפני שמשתמשים בתוצאה. השורה האחרונה מדפיסה False כי Contains משתמש ב-Equals, ומחלקה בלי דריסה של Equals משווה הפניות: Product חדש עם אותם שדות הוא אובייקט אחר. חפשו לפי מאפיין עם Exists או Find, או דרסו את Equals ואת GetHashCode במחלקה.
כל אחת מהמתודות האלה עוברת על הרשימה מההתחלה, ולכן כל קריאה היא O(n). לחיפושים חוזרים לפי מפתח, Dictionary עונה בזמן קבוע.
מיון רשימה
Sort() ממיין במקום לפי הסדר הטבעי של הפריטים. העבירו lambda של השוואה כדי למיין לפי כל דבר אחר.
פלט:
1, 2, 5, 9
9, 5, 2, 1
Ben 95, Ana 120, Chloe 150
Chloe 150, Ben 95, Ana 120
Ben 95, Ana 120, Chloe 150
השוואה מחזירה מספר שלילי כש-a צריך לבוא ראשון, חיובי כש-b צריך לבוא ראשון, ואפס כשהם שווים; CompareTo מפיקה בדיוק את זה. שני הבדלים בין Sort ל-OrderBy של LINQ חשובים בפועל:
Sortמשנה את הרשימה;OrderByמחזיר רצף חדש ולא נוגע ברשימה.Sortלא יציב: פריטים שמשתווים יכולים להחליף מקומות.OrderByיציב, ו-ThenByמוסיף מפתח שני:players.OrderBy(p => p.Score).ThenBy(p => p.Name).
קריאה ל-Sort() על רשימה של מחלקה שלכם בלי השוואה זורקת InvalidOperationException, כי הרשימה לא יודעת איך לסדר את הפריטים. או שתעבירו השוואה, או שתממשו IComparable<T> במחלקה.
Reverse() הופך את הסדר במקום. מכיוון ש-System.Linq מגדיר גם הרחבה בשם Reverse, list.Reverse() על List<T> בוחר בגרסה שעובדת במקום, שלא מחזירה כלום.
לולאות, ומחיקה תוך כדי מעבר
foreach קוראת כל פריט לפי הסדר. לולאת for נותנת גם את האינדקס. מה שאי אפשר לעשות זה להוסיף או למחוק פריטים באמצע foreach על אותה רשימה:
פלט:
Caught InvalidOperationException
120, 80, 60
120, 80, 60
החריגה היא InvalidOperationException עם ההודעה "Collection was modified; enumeration operation may not execute." לולאת for קדימה לא זורקת חריגה, אבל היא מדלגת בשקט על הפריט שבא מיד אחרי כל פריט שמוחקים, כי הכול זז אינדקס אחד אחורה. מעבר מהסוף להתחלה מונע את זה. פתרון שלישי הוא מעבר על עותק: foreach (var t in orders.ToList()).
שינוי של מאפיין של פריט בתוך foreach (למשל order.Status = "sent") בסדר. הוספה או מחיקה של פריטים שוברות את ה-enumerator, וכך גם החלפה של פריט דרך האינדקסר (orders[i] = 0).
המרה בין רשימות למערכים
פלט:
4
3
4, 4, 4, 4
Oslo / Lima / Pune / Kyiv
בשני הכיוונים האיברים מועתקים לאחסון חדש, ולכן שינוי של הרשימה בהמשך לא משפיע על המערך. ConvertAll היא הגרסה של הרשימה עצמה ל-Select(...).ToList() של LINQ. הדפסה של רשימה עם Console.WriteLine(list) מציגה את שם הטיפוס (System.Collections.Generic.List`1[System.String]); השתמשו ב-string.Join.
טבלת עזר מהירה
| משימה | קוד |
|---|---|
| יצירה | var l = new List<int>(); או new List<int> { 1, 2 } |
| הוספה לסוף | l.Add(x), l.AddRange(items) |
| הכנסה במיקום | l.Insert(i, x) |
| מחיקת ההתאמה הראשונה | l.Remove(x) (מחזיר bool) |
| מחיקה לפי אינדקס | l.RemoveAt(i) |
| מחיקה לפי תנאי | l.RemoveAll(x => ...) (מחזיר כמות) |
| מספר הפריטים | l.Count |
| בדיקת הכלה | l.Contains(x), l.Exists(x => ...) |
| חיפוש | l.Find(...), l.FindAll(...), l.FindIndex(...) |
| מיון במקום | l.Sort(), l.Sort((a, b) => ...) |
| עותק ממוין | l.OrderBy(x => ...).ToList() |
| למערך | l.ToArray() |
טעויות נפוצות
- מחיקה בתוך
foreach. זורקתInvalidOperationException; השתמשו ב-RemoveAllאו בלולאתforמהסוף להתחלה. - ציפייה ש-
Remove(x)ימחק כל עותק. הוא מוחק את הראשון; השתמשו ב-RemoveAll. - שימוש ב-
Containsעל אובייקטים בליEquals. הוא משווה הפניות; חפשו לפי מאפיין עםExists. - גישה לפי אינדקס לרשימה שנוצרה עם capacity.
new List<int>(10)ריקה; הוסיפו קודם פריטים. - הנחה ש-
Sortשומר על הסדר בין פריטים שווים. הוא לא; השתמשו ב-OrderByכשהסדר בין שווים חשוב. - השמה של רשימה כדי לשתף אותה, ואז הפתעה משינויים.
var copy = list;היא אותה רשימה;new List<T>(list)הוא עותק.
שאלות נפוצות
איך מוסיפים פריטים ל-List ב-C#?
list.Add(item) מוסיף פריט אחד לסוף, list.AddRange(otherCollection) מוסיף הרבה, ו-list.Insert(index, item) שם פריט במיקום מסוים ומזיז את השאר קדימה. אפשר גם למלא רשימה כשיוצרים אותה: var names = new List<string> { "Ana", "Ben" };.
איך מוחקים פריט מ-List ב-C#?
Remove(value) מוחק את הפריט התואם הראשון ומחזיר true אם מצא כזה. RemoveAt(index) מוחק לפי מיקום. RemoveAll(x => condition) מוחק כל פריט שמתאים ומחזיר כמה הוא מחק. Clear() מרוקן את הרשימה.
איך בודקים אם List מכילה ערך ב-C#?
list.Contains(value) מחזיר true או false. הוא משווה עם Equals, ולכן במחלקות שלכם הוא משווה הפניות, אלא אם המחלקה דורסת את Equals. כדי לבדוק לפי מאפיין, השתמשו ב-list.Exists(p => p.Name == "Ana") או ב-list.Any(...) של LINQ.
איך ממיינים List לפי מאפיין ב-C#?
העבירו השוואה ל-Sort: people.Sort((a, b) => a.Age.CompareTo(b.Age)) ממיין במקום. החליפו בין a ל-b לסדר יורד. או השתמשו ב-LINQ, שמחזיר רצף ממוין חדש ושומר פריטים שווים בסדר המקורי שלהם: people.OrderBy(p => p.Age).ToList().
למה אני מקבל "Collection was modified; enumeration operation may not execute"?
הוספתם, מחקתם או החלפתם (list[i] = x) פריטים בזמן ש-foreach עברה על אותה רשימה, וה-enumerator זורק InvalidOperationException בצעד הבא שלו. השתמשו ב-list.RemoveAll(condition), עברו מהסוף להתחלה עם לולאת for, או עברו על עותק כמו list.ToList().
מה ההבדל בין Count ל-Capacity?
Count הוא מספר הפריטים ברשימה. Capacity הוא הגודל של המערך הפנימי, שהוא לפחות Count. כשהמערך מתמלא, הרשימה מקצה מערך חדש בגודל כפול ומעתיקה את הפריטים, ולכן Add מהיר בממוצע.