أسرار foreach في C#: كيفية الحصول على فهرس التكرار الحالي

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

لماذا لا يوفر foreach الفهرس "من الصندوق"؟ وماذا تفعل حيال ذلك؟

\n\n

هل سبق لك أن كتبت حلقة foreach في C# وأدركت فجأة: "أحتاج إلى رقم العنصر الحالي!"، تمتد يدك إلى لوحة المفاتيح لكتابة for (int i = 0; i < list.Count; i++)... توقف! ماذا لو كانت المجموعة هي IEnumerable التي لا تحتوي على مفهرس؟ أو كنت تعمل مع HashSet؟ هنا يفشل for القديم الجيد.

\n\n

هذا السؤال هو ضربة حقيقية على Stack Overflow (ملايين المشاهدات!). وليس من دون سبب. foreach هو ملك تكرار المجموعات: فهو موجز وآمن ويعمل مع أي نوع قابل للتعداد. لكن لعنته هي عدم وجود عداد مدمج. اليوم سنصلح هذا. سأريكم 4 طرق مثبتة للحصول على الفهرس في foreach، وسأحلل إيجابياتها وسلبياتها وأداءها. هيا بنا!

\n\n

الطريقة رقم 1. الكلاسيكية: عداد خارجي

\n\n

أبسط وأكثر الطرق بديهية. نقوم بإنشاء متغير عداد قبل الحلقة وزيادته داخل الجسم.

\n\n
List<string> fruits = new List<string>() { "تفاحة", "موز", "كرز", "بطيخ" };\nint index = 0;\nforeach (var fruit in fruits)\n{\n    Console.WriteLine($"الفهرس {index}: {fruit}");\n    index++;\n}
\n\n

الإيجابيات:

\n
    \n
  • شفاف للغاية. يُقرأ الكود في ثانية.
  • \n
  • لا يتطلب مكتبات إضافية أو LINQ.
  • \n
  • يعمل مع أي IEnumerable.
  • \n
\n\n

السلبيات:

\n
    \n
  • يجب ألا تنسى تعريف العداد وزيادته.
  • \n
  • إذا كان داخل الحلقة continue، فسيظل العداد يزيد (هل تريد ذلك؟).
  • \n
  • في الحلقات المتداخلة، من السهل الخلط بين أي index ينتمي إلى ماذا.
  • \n
\n\n

هذه الطريقة مثل السكين السويسري: موثوقة ولكنها أساسية. لـ 80% من المهام، هي أكثر من كافية.

\n\n

الطريقة رقم 2. LINQ Select مع الفهرس: الأناقة في سطر واحد

\n\n

إذا كنت تحب الأسلوب الوظيفي، فإن LINQ سيأتي للإنقاذ. طريقة Select() لها تحميل زائد ينقل فهرس العنصر.

\n\n
List<string> fruits = new List<string>() { "تفاحة", "موز", "كرز", "بطيخ" };\n\nforeach (var (fruit, index) in fruits.Select((item, i) => (item, i)))\n{\n    Console.WriteLine($"الفهرس {index}: {fruit}");\n}
\n\n

لاحظ تفكيك الصف (tuple) مباشرة في رأس الحلقة. هذه واحدة من أجمل ميزات C# الحديثة (بدءًا من الإصدار 7.0).

\n\n

الإيجابيات:

\n
    \n
  • يصبح الكود تصريحيًا: "خذ المجموعة، أضف إلى كل عنصر فهرسه".
  • \n
  • العداد لا يتسرب إلى الخارج - فهو مغلف داخل LINQ.
  • \n
  • يتناسب بشكل رائع مع سلاسل طرق LINQ الأخرى.
  • \n
\n\n

السلبيات:

\n
    \n
  • بعض الحمل الإضافي (overhead) لإنشاء الصفوف (tuples). بالنسبة للمجموعات التي تصل إلى 10000 عنصر، هذا غير ملحوظ، لكنه قد يكون حرجًا للقوائم الضخمة.
  • \n
  • يتطلب استيراد System.Linq.
  • \n
\n\n

هذه الطريقة هي اختيار من يكتب كودًا "نظيفًا". إنها جيدة بشكل خاص عندما يكون الفهرس مطلوبًا ليس للتحكم في الحلقة، بل لتمريره إلى طريقة أخرى.

\n\n

الطريقة رقم 3. المفهرس عبر ElementAt(): عندما تكون المجموعة IEnumerable

\n\n

أحيانًا يكون لديك فقط IEnumerable، بدون مفهرس، ولكنك تريد بشدة الحصول على عنصر برقمه. تقنيًا يمكنك القيام بذلك:

\n\n
IEnumerable<string> fruits = new List<string>() { "تفاحة", "موز", "كرز", "بطيخ" };\n\nfor (int i = 0; i < fruits.Count(); i++) // انتبه: هذا كود سيء!\n{\n    var fruit = fruits.ElementAt(i);\n    Console.WriteLine($"الفهرس {i}: {fruit}");\n}
\n\n

توقف! لا تفعل هذا أبدًا. Count() و ElementAt() لـ IEnumerable تعملان بتعقيد O(n) لكل منهما. ستتحول حلقتك إلى تعقيد O(n²) - تعقيد تربيعي. على 1000

المدونات

توصيات الكتب