[C#] هل هناك سبب لإعادة استخدام C # للمتغير في foreach؟


Answers

ما يتم طرحه يتم تغطيته بشكل كامل من قبل Eric Lippert في مقالته في المدونة إن إغلاق متغير الحلقة يعتبر ضارًا وتكملة له.

بالنسبة لي ، فإن الحجة الأكثر إقناعاً هي أن وجود متغير جديد في كل عملية تكرار لن يكون متناسقًا مع حلقة النمط for(;;) . هل تتوقع أن يكون لديك int i جديد في كل تكرار for (int i = 0; i < 10; i++) ؟

المشكلة الأكثر شيوعًا في هذا السلوك هي إجراء إغلاق على متغير التكرار ولديه حل سهل:

foreach (var s in strings)
{
    var s_for_closure = s;
    query = query.Where(i => i.Prop == s_for_closure); // access to modified closure

مشاركة المدونة الخاصة بي حول هذه المشكلة: الإغلاق على المتغير foreach في C # .

Question

عند استخدام تعبيرات lambda أو أساليب مجهولة في C # ، يجب أن نكون حذرين من الوصول إلى pedfall تعديل الإغلاق . فمثلا:

foreach (var s in strings)
{
   query = query.Where(i => i.Prop == s); // access to modified closure
   ...
}

نظرًا للإغلاق المعدل ، سيؤدي الرمز أعلاه إلى أن تستند جميع عبارات Where في الاستعلام إلى القيمة النهائية لـ s .

كما هو موضح here ، يحدث هذا لأنه يتم ترجمة متغير s تعريفه في حلقة foreach أعلاه مثل هذا في المحول البرمجي:

string s;
while (enumerator.MoveNext())
{
   s = enumerator.Current;
   ...
}

بدلا من هذا القبيل:

while (enumerator.MoveNext())
{
   string s;
   s = enumerator.Current;
   ...
}

كما هو موضح here ، لا توجد مزايا أداء لإعلان متغير خارج الحلقة ، وفي ظل الظروف العادية يكون السبب الوحيد الذي يمكنني التفكير فيه للقيام بذلك هو إذا كنت تخطط لاستخدام المتغير خارج نطاق الحلقة:

string s;
while (enumerator.MoveNext())
{
   s = enumerator.Current;
   ...
}
var finalString = s;

لكن لا يمكن استخدام المتغيرات المحددة في حلقة foreach خارج الحلقة:

foreach(string s in strings)
{
}
var finalString = s; // won't work: you're outside the scope.

لذا ، يعلن المترجم المتغير بطريقة تجعله عرضة لخطأ غالبًا ما يصعب العثور عليه وتصحيحه ، بينما لا ينتج عنه أي فوائد يمكن رؤيتها.

هل هناك شيء يمكنك القيام به مع حلقات foreach بهذه الطريقة التي لا يمكن إذا تم تجميعها مع متغير داخل النطاق ، أو هذا هو مجرد خيار عشوائي الذي تم إجراؤه قبل أساليب مجهولة المصدر وتعبيرات لامدا كانت متاحة أو مشتركة ، والتي لم تنقح منذ ذلك الحين؟




في C # 5.0 ، تم إصلاح هذه المشكلة ويمكنك إغلاق أكثر من متغيرات حلقة والحصول على النتائج التي تتوقعها.

مواصفات اللغة تقول:

8.8.4 بيان foreach

(...)

بيان foreach للنموذج

foreach (V v in x) embedded-statement

ثم يتم توسيعه إلى:

{
  E e = ((C)(x)).GetEnumerator();
  try {
      while (e.MoveNext()) {
          V v = (V)(T)e.Current;
          embedded-statement
      }
  }
  finally {
      … // Dispose e
  }
}

(...)

يعتبر موضع v داخل حلقة while مهمًا لكيفية التقاطها بواسطة أي دالة مجهولة تحدث في العبارة المضمنة. فمثلا:

int[] values = { 7, 9, 13 };
Action f = null;
foreach (var value in values)
{
    if (f == null) f = () => Console.WriteLine("First value: " + value);
}
f();

إذا تم الإعلان عن v خارج حلقة while ، فسيتم مشاركتها بين جميع التكرارات ، وقيمتها بعد الحلقة forc ستكون القيمة النهائية ، 13 ، وهو ما سوف يطبعه طلب f . بدلاً من ذلك ، نظرًا لأن كل تكرار له المتغير الخاص به ، فسيستمر التكرار الذي تم التقاطه بواسطة f في التكرار الأول في الاحتفاظ بالقيمة 7 ، وهو ما سيتم طباعته. ( ملاحظة: الإصدارات السابقة من C # أعلنت v خارج الحلقة أثناء. )