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




2 Answers

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

انتقادك له ما يبرره تماما.

أناقش هذه المشكلة بالتفصيل هنا:

اختتام متغير حلقة تعتبر ضارة

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

الأخير. لم تحدد مواصفات C # 1.0 في الواقع ما إذا كان متغير الحلقة داخل أو خارج جسم الحلقة ، لأنه لم يحدث فرق ملحوظ. عندما تم تقديم دلالات الإغلاق في C # 2.0 ، تم إجراء الاختيار لوضع متغير الحلقة خارج الحلقة ، بما يتفق مع حلقة "for".

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

لن يتم تغيير حلقة for ولن يكون التغيير "backeded" إلى الإصدارات السابقة من C #. لذلك يجب عليك الاستمرار في توخي الحذر عند استخدام هذا المصطلح.

in void

عند استخدام تعبيرات 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 بهذه الطريقة التي لا يمكن إذا تم تجميعها مع متغير داخل النطاق ، أو هذا هو مجرد خيار عشوائي الذي تم إجراؤه قبل أساليب مجهولة المصدر وتعبيرات لامدا كانت متاحة أو مشتركة ، والتي لم تنقح منذ ذلك الحين؟




بعد أن عشت من هذا ، لدي عادة من تضمين متغيرات محددة محليا في النطاق الأعمق الذي يمكنني استخدامه لنقل إلى أي إغلاق. في المثال الخاص بك:

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

أنا افعل:

foreach (var s in strings)
{
    string search = s;
    query = query.Where(i => i.Prop == search); // New definition ensures unique per iteration.

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




Related

c# foreach lambda scope anonymous-methods