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] عليها ترمي استثناءً. السعة مساحة لا عناصر.
البحث: 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") مقبول. أما إضافة العناصر أو حذفها فتكسر المعدِّد، وكذلك استبدال عنصر عبر المفهرس (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. - الفهرسة في قائمة أُنشئت بسعة.
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 تمرّ على القائمة نفسها، فيرمي المعدِّد InvalidOperationException في خطوته التالية. استخدم list.RemoveAll(condition)، أو مرّ بشكل معكوس بحلقة for، أو مرّ على نسخة مثل list.ToList().
ما الفرق بين Count وCapacity؟
Count عدد العناصر في القائمة. وCapacity حجم المصفوفة الداخلية، وهو على الأقل Count. حين تمتلئ المصفوفة تحجز القائمة مصفوفة جديدة بضعف الحجم وتنسخ العناصر، فتكون Add سريعة في المتوسط.