为什么这样做不好的lambda表达式使用迭代变量为什么这样做不好的lambda表达式使用迭代变量(W

2019-05-09 03:47发布

我只是写一些简单的代码,发现这个错误编译

在一个lambda表达式使用迭代变量可以具有意想不到的效果。
取而代之的是,建立在循环中的局部变量,并为其分配迭代变量的值。

我知道这意味着什么,我可以很容易地解决这个问题,不是一个大问题。
但我不知道为什么这是一个坏主意的拉姆达使用迭代变量?
我可能会导致什么问题以后?

Answer 1:

考虑以下代码:

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年回来,所以从那时起实际行为可能已经改变。



Answer 2:

假设你的意思是C#在这里。

这是因为编译器实现封闭的方式。 使用迭代变量可能会导致访问修改关闭(注意一个问题,我说“可以”而不是“会”造成一个问题,因为有时不取决于还有什么是在方法发生,有时候你真的想访问修改闭合)。

更多信息:

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

甚至更多的信息:

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

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

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



Answer 3:

在.NET闭包理论

局部变量:范围与寿命(加闭包) (存档2010)

(重点煤矿)

会发生什么事在这种情况下,我们使用一个封闭。 闭包是一种特殊的结构,居住其中包含了需要用其他方法被称为局部变量的方法之外。 当查询是指一个局部变量(或参数),该变量是由封闭捕获并给变量的所有引用将被重定向到闭合。

当你正在考虑关闭在.NET中是如何工作的,我建议牢记这些要点,这是设计者们什么时候被执行此功能一起工作:

  • 请注意,“变量捕获”和lambda表达式是不是IL功能,VB.NET(和C#)不得不使用现有的工具来实现这些功能,在这种情况下,类和Delegate秒。
  • 或者换一种说法,局部变量不能真正坚持超越其范围。 什么语言确实是使它看起来像他们可以,但它不是一个完美的抽象。
  • Func(Of T) (即Delegate )情况下没有办法来存储传递到它们的参数。
  • 虽然, Func(Of T)做存储类的实例中的方法是的一部分。 这是大道用于“记住”传递到Lambda表达式参数的.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的”,在这篇文章中,但在我看来,这是非常不可能的,这些限制将显着改变; 如果你找到一个设置,你不能复制这些结果,请给我留下了评论。

“但JRH你为什么要发布一个迟到的回答?”

  • 在这篇文章的链接页面丢失或一团糟。
  • 有这个vb.net没有vb.net答案标记为问题,因为写作的时候有一个C#(错误的语言)的答案和链接大多只回答(3个死链接)。


文章来源: Why is it bad to use an iteration variable in a lambda expression