Menu

List ב-C#: Add, Remove, Contains, Find ו-Sort

List<T> הוא המערך הגדל של C#. למדו איך יוצרים רשימה, מוסיפים ומכניסים פריטים, מוחקים לפי ערך, אינדקס או תנאי, מחפשים עם Contains ו-Find, ממיינים לפי מאפיין, ונמנעים מהשגיאה של שינוי רשימה בתוך foreach.

בדף הזה יש עורכים שאפשר להריץ - לערוך, להריץ ולראות את הפלט מיד.

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 מהיר בממוצע.

איור של שפות התכנות ב-Coddy

ללמוד תכנות עם Coddy

להתחיל