لماذا يعتبر استخدام متغير التكرار في تعبير لامدا أمرًا سيئًا؟

StackOverflow https://stackoverflow.com/questions/227820

سؤال

كنت أكتب للتو بعض التعليمات البرمجية السريعة ولاحظت هذا الخطأ المتوافق

قد يؤدي استخدام متغير التكرار في تعبير لامدا إلى نتائج غير متوقعة.
بدلاً من ذلك، قم بإنشاء متغير محلي داخل الحلقة وقم بتعيين قيمة متغير التكرار له.

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

هل كانت مفيدة؟

المحلول

والنظر في هذا الرمز:

List<Action> actions = new List<Action>();

for (int i = 0; i < 10; i++)
{
    actions.Add(() => Console.WriteLine(i));
}

foreach (Action action in actions)
{
    action();
}

وماذا تتوقع لطباعة؟ الجواب الواضح هو 0 ... 9 - ولكن في الواقع فإنه يطبع 10، عشر مرات. انها لأنه لا يوجد واحد فقط المتغير الذي تم القبض عليه من قبل جميع الوفود. انه هذا النوع من السلوك الذي هو غير متوقع.

وتحرير: رأيت للتو أن كنت تتحدث عن VB.NET بدلا من C #. أعتقد VB.NET ديه حتى قواعد أكثر تعقيدا، بسبب الطريقة المتغيرات الحفاظ على قيمها عبر التكرار. هذه الوظيفة جاريد بارسونز يعطي بعض المعلومات عن هذا النوع من الصعوبات التي ينطوي - على الرغم من أنه عاد من 2007، لذلك قد تغيرت السلوك الفعلي منذ ذلك الحين

نصائح أخرى

وعلى افتراض تقصد C # هنا.

وانها بسبب الطريقة المترجم ينفذ الإغلاق. باستخدام التكرار المتغير <م> يمكن يسبب مشكلة مع الوصول إلى إغلاق المعدلة (لاحظ أن قلت "يمكن" وليس "سوف" يسبب مشكلة لأن في بعض الأحيان لا يحدث اعتمادا على ما هو موجود في الأسلوب ، وأحيانا كنت فعلا تريد الوصول إلى إغلاق المعدلة).

ومزيد من المعلومات:

http://blogs.msdn.com/abhinaba/ أرشيف / 2005/10/18 / 482180.aspx

وحتى على مزيد من المعلومات:

http://blogs.msdn.com/oldnewthing/ أرشيف / 2006/08/02 / 686456.aspx

http://blogs.msdn.com/oldnewthing/ أرشيف / 2006/08/03 / 687529.aspx

http://blogs.msdn.com/oldnewthing/ أرشيف / 2006/08/04 / 688527.aspx

نظرية الإغلاقات في .NET

المتغيرات المحلية:النطاق مقابلالعمر (بالإضافة إلى عمليات الإغلاق) (أرشفة 2010)

(التأكيد على الألغام)

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

عندما تفكر في كيفية عمل عمليات الإغلاق في .NET، أوصي بوضع هذه النقاط في الاعتبار، وهذا ما كان على المصممين التعامل معه عند تنفيذ هذه الميزة:

  • لاحظ أن تعبيرات "الالتقاط المتغير" وتعبيرات lambda ليست من ميزات IL، وكان على VB.NET (و C#) تنفيذ هذه الميزات باستخدام الأدوات الموجودة، في هذه الحالة، الفئات و Delegateس.
  • أو بعبارة أخرى، لا يمكن للمتغيرات المحلية أن تستمر خارج نطاقها.ما تفعله اللغة هو أن تصنعه يبدو بقدر استطاعتهم، لكنها ليست فكرة مجردة مثالية.
  • Func(Of T) (أي.، Delegate) لا تملك المثيلات طريقة لتخزين المعلمات التي تم تمريرها إليها.
  • رغم ذلك، Func(Of T) قم بتخزين مثيل الفئة التي تعد الطريقة جزءًا منها.هذا هو الطريق الذي يستخدمه إطار عمل .NET "لتذكر" المعلمات التي تم تمريرها إلى تعبيرات لامدا.

حسنا دعونا نلقي نظرة!

عينة من الرموز:

لنفترض أنك كتبت بعض التعليمات البرمجية مثل هذا:

' Prints 4,4,4,4
Sub VBDotNetSample()
    Dim funcList As New List(Of Func(Of Integer))

    For indexParameter As Integer = 0 To 3
        'The compiler says:
        '   Warning     BC42324 Using the iteration variable in a lambda expression may have unexpected results.  
        '   Instead, create a local variable within the loop and assign it the value of the iteration variable

        funcList.Add(Function()indexParameter)

    Next


    For Each lambdaFunc As Func(Of Integer) In funcList
        Console.Write($"{lambdaFunc()}")

    Next

End Sub

ربما تتوقع أن يطبع الكود 0,1,2,3، لكنه في الواقع يطبع 4,4,4,4، وذلك لأن indexParameter تم "القبض" في نطاق Sub VBDotNetSample()نطاق، وليس في For نطاق الحلقة.

كود عينة مفككة

شخصيًا، أردت حقًا معرفة نوع التعليمات البرمجية التي أنشأها المترجم لهذا الغرض، لذلك مضيت واستخدمت JetBrains DotPeek.أخذت الكود الذي أنشأه المترجم وقمت بترجمته يدويًا إلى VB.NET.

التعليقات والأسماء المتغيرة لي.تم تبسيط الكود قليلاً بطرق لا تؤثر على سلوك الكود.

Module Decompiledcode
    ' Prints 4,4,4,4
    Sub CompilerGenerated()

        Dim funcList As New List(Of Func(Of Integer))

        '***********************************************************************************************
        ' There's only one instance of the closureHelperClass for the entire Sub
        ' That means that all the iterations of the for loop below are referencing
        ' the same class instance; that means that it can't remember the value of Local_indexParameter
        ' at each iteration, and it only remembers the last one (4).
        '***********************************************************************************************
        Dim closureHelperClass As New ClosureHelperClass_CompilerGenerated

        For closureHelperClass.Local_indexParameter = 0 To 3

            ' NOTE that it refers to the Lambda *instance* method of the ClosureHelperClass_CompilerGenerated class, 
            ' Remember that delegates implicitly carry the instance of the class in their Target 
            ' property, it's not just referring to the Lambda method, it's referring to the Lambda
            ' method on the closureHelperClass instance of the class!
            Dim closureHelperClassMethodFunc As Func(Of Integer) = AddressOf closureHelperClass.Lambda
            funcList.Add(closureHelperClassMethodFunc)

        Next
        'closureHelperClass.Local_indexParameter is 4 now.

        'Run each stored lambda expression (on the Delegate's Target, closureHelperClass)
        For Each lambdaFunc As Func(Of Integer) in funcList      

            'The return value will always be 4, because it's just returning closureHelperClass.Local_indexParameter.
            Dim retVal_AlwaysFour As Integer = lambdaFunc()

            Console.Write($"{retVal_AlwaysFour}")

        Next

    End Sub

    Friend NotInheritable Class ClosureHelperClass_CompilerGenerated
        ' Yes the compiler really does generate a class with public fields.
        Public Local_indexParameter As Integer

        'The body of your lambda expression goes here, note that this method
        'takes no parameters and uses a field of this class (the stored parameter value) instead.
        Friend Function Lambda() As Integer
            Return Me.Local_indexParameter

        End Function

    End Class

End Module

لاحظ كيف يوجد مثيل واحد فقط لـ closureHelperClass لكامل الجسم Sub CompilerGenerated, ، لذلك لا توجد طريقة تمكن الدالة من طباعة الوسيط For قيم مؤشر الحلقة 0،1،2،3 (لا يوجد مكان لتخزين هذه القيم).يطبع الكود 4 فقط، قيمة الفهرس النهائية (بعد For حلقة) أربع مرات.

الحواشي:

  • يوجد ضمنيًا "اعتبارًا من .NET 4.6.1" في هذا المنشور، ولكن في رأيي من غير المرجح أن تتغير هذه القيود بشكل كبير؛إذا وجدت إعدادًا لا يمكنك فيه إعادة إنتاج هذه النتائج، فيرجى ترك تعليق لي.

"لكن لماذا نشرت إجابة متأخرة؟"

  • الصفحات المرتبطة بهذا المنشور إما مفقودة أو في حالة من الفوضى.
  • لم تكن هناك إجابة بـ vb.net على هذا السؤال الموسوم بـ vb.net، وحتى وقت كتابة هذا التقرير كانت هناك إجابة C# (لغة خاطئة) وإجابة في الغالب مرتبطة فقط (مع 3 روابط معطلة).
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top