لماذا يعيد C# استخدام المتغير في foreach؟ شرح للمبتدئين

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

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

ابدأ الدورة

مقدمة: اللغز الذي يحير المبتدئين

\n\n

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

\n\n

على Stack Overflow، السؤال "هل هناك سبب لإعادة استخدام C# للمتغير في foreach؟" حصد آلاف المشاهدات والعديد من التعليقات. هذه ليست مجرد مسألة أكاديمية - إنها فخ عملي يقع فيه حتى المطورون ذوو الخبرة. في هذا المقال، سنفهم لماذا تفعل C# ذلك، وكيف تغير الأمر مع إصدارات اللغة، والأهم من ذلك، كيف تتجنب الأخطاء المرتبطة بهذا السلوك.

\n\n

سنتحدث بلغة بسيطة، مع أمثلة برمجية، حتى يفهم المبتدئ في C# جوهر المشكلة. هيا بنا!

\n\n

جوهر المشكلة: ماذا يعني "إعادة استخدام المتغير"؟

\n\n

في حلقة foreach التقليدية في C# (قبل الإصدار 5.0)، كان يُعلن متغير التكرار مرة واحدة طوال الحلقة، وليس إنشاءه من جديد لكل تكرار. هذا يعني أنه إذا التقطت هذا المتغير في إغلاق (مثل تعبير لامدا أو دالة مجهولة)، فإن جميع الإغلاقات ستشير إلى نفس المتغير.

\n\n

لننظر إلى المثال التقليدي للمشكلة:

\n\n
var actions = new List<Action>();\nvar numbers = new List<int> { 1, 2, 3, 4, 5 };\n\nforeach (var number in numbers)\n{\n    actions.Add(() => Console.WriteLine(number));\n}\n\n// تشغيل جميع الإجراءات\nforeach (var action in actions)\n{\n    action();\n}\n
\n\n

السؤال: ماذا سيطبع هذا الكود؟ بشكل بديهي، يبدو أنه سيطبع 1, 2, 3, 4, 5. لكن في الواقع (قبل C# 5) كان سيطبع 5, 5, 5, 5, 5.

\n\n

لماذا؟ لأن المتغير number موجود في نسخة واحدة طوال الحلقة. عندما تنتهي الحلقة، تكون قيمته مساوية لآخر عنصر (5). جميع تعبيرات لامدا التقطت مرجعًا لهذا المتغير، وليس قيمته في لحظة الإنشاء.

\n\n

تشبيه للفهم

\n\n

تخيل أن لديك صندوقًا (متغيرًا)، وتضع فيه أشياء مختلفة (قيمًا) واحدة تلو الأخرى. أنت تعطي أصدقاءك (تعبيرات لامدا) ليس الأشياء نفسها، بل مفتاح الصندوق. عندما يأتي الأصدقاء لاستلام أشيائهم، يفتحون الصندوق ليجدوا آخر شيء وضعته فيه. هكذا يعمل foreach القديم.

\n\n

السياق التاريخي: لماذا فعلوا ذلك؟

\n\n

يسأل العديد من المبتدئين: "هذا خطأ! لماذا يفعلون ذلك؟" في الحقيقة، كان هذا قرارًا واعيًا من مطوري C#، بناءً على الأداء وسهولة الترجمة.

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

لكن المجتمع سرعان ما اكتشف هذه "الميزة" وبدأ في كتابة تقارير الأخطاء. على Stack Overflow، أصبح سؤال "هل هناك سبب لإعادة استخدام C# للمتغير في foreach؟" من أكثر الأسئلة شيوعًا. استجابت Microsoft، وفي C# 5.0 (عام 2012) تم تغيير السلوك.

\n\n

ما الذي تغير في C# 5.0؟

\n\n

بدءًا من C# 5.0، يقوم المترجم بإنشاء متغير جديد لكل تكرار من حلقة foreach. الآن، سيعمل المثال أعلاه بشكل بديهي صحيح وسيطبع 1, 2, 3, 4, 5.

\n\n
// هذا                

المدونات

توصيات الكتب