Frage

Ich schrieb gerade einen schnellen Code und bemerkte diesen komplizierten Fehler

Die Verwendung der Iterationsvariablen in einem Lambda -Ausdruck kann unerwartete Ergebnisse haben.
Erstellen Sie stattdessen eine lokale Variable in der Schleife und weisen Sie sie den Wert der Iterationsvariablen zu.

Ich weiß, was es bedeutet und kann es leicht reparieren, keine große Sache.
Aber ich habe mich gefragt, warum es eine schlechte Idee ist, eine Iterationsvariable in einem Lambda zu verwenden.
Welche Probleme kann ich später verursachen?

War es hilfreich?

Lösung

Betrachten Sie diesen Code:

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

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

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

Was würden Sie erwarten, dass dies druckt? Die offensichtliche Antwort ist 0 ... 9 - aber tatsächlich druckt sie 10, zehnmal. Es liegt nur daran, dass es nur eine Variable gibt, die von allen Delegierten erfasst wird. Es ist diese Art von Verhalten, das unerwartet ist.

EDIT: Ich habe gerade gesehen, dass Sie über VB.NET statt C#sprechen. Ich glaube, VB.NET hat aufgrund der Art und Weise, wie Variablen ihre Werte über die Iterationen hinweg aufrechterhalten, noch kompliziertere Regeln. Dieser Beitrag von Jared Parsons Gibt einige Informationen über die Art der damit verbundenen Schwierigkeiten - obwohl es von 2007 zurück ist, so dass sich das tatsächliche Verhalten seitdem möglicherweise geändert hat.

Andere Tipps

Angenommen, Sie meinen C# hier.

Es liegt an der Art und Weise, wie der Compiler Schließungen implementiert. Verwenden einer Iterationsvariablen kann verursachen ein Problem mit dem Zugriff auf einen modifizierten Verschluss (beachten Sie, dass ich sagte, dass "nicht" ein Problem "nicht" verursacht ", da dies manchmal nicht geschieht, je nachdem, was noch in der Methode ist, und manchmal möchten Sie tatsächlich auf den modifizierten Verschluss zugreifen) .

Mehr Info:

http://blogs.msdn.com/abhinaba/archive/2005/10/18/482180.aspx

Noch mehr Infos:

http://blogs.msdn.com/oldnewing/archive/2006/08/02/686456.aspx

http://blogs.msdn.com/oldnewing/archive/2006/08/03/687529.aspx

http://blogs.msdn.com/oldnewing/archive/2006/08/04/688527.aspx

Theorie der Schließungen in .NET

Lokale Variablen: Umfang vs. Lebensdauer (plus Schließungen) (Archiviertes 2010)

(Hervorhebung meiner)

Was in diesem Fall passiert, ist, dass wir einen Verschluss verwenden. Ein Schließ ist nur eine besondere Struktur, die außerhalb der Methode lebt, die die lokalen Variablen enthält, auf die von anderen Methoden bezeichnet werden müssen. Wenn sich eine Abfrage auf eine lokale Variable (oder einen Parameter) bezieht, wird diese Variable durch den Verschluss erfasst und alle Verweise auf die Variable werden auf den Schließ umgeleitet.

Wenn Sie darüber nachdenken, wie Schließungen in .NET funktionieren, empfehle ich, diese Aufzählungszeichen im Auge zu behalten. Damit mussten die Designer diese Funktion implementieren:

  • Beachten Sie, dass "variable Erfassung" und Lambda -Ausdrücke keine IL -Funktion sind, VB.NET (und C#) mussten diese Funktionen mithilfe vorhandener Tools, in diesem Fall, Klassen und implementieren, implementieren Delegates.
  • ODER anders ausgedrückt, lokale Variablen können nicht wirklich über ihren Geltungsbereich hinausgehen. Was die Sprache tut, ist es zu machen erscheinen Wie sie können, aber es ist keine perfekte Abstraktion.
  • Func(Of T) (dh,, Delegate) Beispiele haben keine Möglichkeit, Parameter zu speichern, die in sie übergeben wurden.
  • Obwohl, Func(Of T) Speichern Sie die Instanz der Klasse. Die Methode ist ein Teil von. Dies ist der Weg, den das .NET -Framework zum "Erinnerung" -Parameter in Lambda -Ausdrücke verwendet hat.

Lassen Sie uns einen Blick darauf werfen!

Beispielcode:

Nehmen wir also an, Sie haben einen solchen Code geschrieben:

' 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

Möglicherweise erwarten Sie, dass der Code 0,1,2,3 druckt, aber tatsächlich 4,4,4,4 druckt. Dies liegt daran indexParameter wurde im Umfang von "gefangen" Sub VBDotNetSample()SKOPE und nicht in der For Schleifenfernrohr.

Zerlegtes Beispielcode

Persönlich wollte ich unbedingt sehen, welche Art von Code der Compiler dafür generierte, also habe ich Jetbrains Dotpeek verwendet. Ich nahm den Compiler generierten Code und übersetzt ihn zurück in VB.NET.

Kommentare und variable Namen meine. Der Code wurde in einer Weise geringfügig vereinfacht, die das Verhalten des Codes nicht beeinflusst.

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

Beachten Sie, wie nur eine Instanz von gibt closureHelperClass für den gesamten Körper von Sub CompilerGenerated, Es gibt also keine Möglichkeit, dass die Funktion das Zwischenprodukt ausdrucken könnte For Schleifenindexwerte von 0,1,2,3 (es gibt keinen Platz, um diese Werte zu speichern). Der Code druckt nur 4, den endgültigen Indexwert (nach dem For Schleife) viermal.

Fußnoten:

  • In diesem Beitrag gibt es eine implizite "Abnutzung von .NET 4.6.1", aber meiner Meinung nach ist es sehr unwahrscheinlich, dass sich diese Einschränkungen dramatisch ändern würden. Wenn Sie ein Setup finden, bei dem Sie diese Ergebnisse nicht reproduzieren können, hinterlassen Sie mir bitte einen Kommentar.

"Aber JRH, warum hast du eine späte Antwort gepostet?"

  • Die in diesem Beitrag verknüpften Seiten fehlen entweder oder in Shambles.
  • Zum Zeitpunkt des Schreibens gab es keine VB.NET -Antwort auf diese Frage mit VB.NET -Tagge. Zum Zeitpunkt des Schreibens gibt es eine C# (falsche Sprache) und eine meist Link -Antwort (mit 3 toten Links).
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top